padrino-admin 0.16.2 → 0.17.0

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +3 -3
  3. data/lib/padrino-admin/access_control.rb +15 -3
  4. data/lib/padrino-admin/generators/actions.rb +1 -1
  5. data/lib/padrino-admin/generators/admin_app.rb +8 -9
  6. data/lib/padrino-admin/generators/admin_page.rb +7 -2
  7. data/lib/padrino-admin/generators/orm.rb +26 -32
  8. data/lib/padrino-admin/generators/templates/account/seeds.rb.tt +7 -2
  9. data/lib/padrino-admin/generators/templates/app.rb.tt +2 -1
  10. data/lib/padrino-admin/generators/templates/assets/javascripts/jquery-3.7.1.min.js +2 -0
  11. data/lib/padrino-admin/generators/templates/erb/app/base/index.erb.tt +1 -1
  12. data/lib/padrino-admin/generators/templates/erb/app/layouts/application.erb.tt +1 -1
  13. data/lib/padrino-admin/generators/templates/erb/app/sessions/new.erb.tt +1 -1
  14. data/lib/padrino-admin/generators/templates/haml/app/base/index.haml.tt +1 -1
  15. data/lib/padrino-admin/generators/templates/haml/app/layouts/application.haml.tt +1 -1
  16. data/lib/padrino-admin/generators/templates/haml/app/sessions/new.haml.tt +1 -1
  17. data/lib/padrino-admin/generators/templates/slim/app/base/index.slim.tt +1 -1
  18. data/lib/padrino-admin/generators/templates/slim/app/layouts/application.slim.tt +1 -1
  19. data/lib/padrino-admin/generators/templates/slim/app/sessions/new.slim.tt +1 -1
  20. data/lib/padrino-admin.rb +1 -1
  21. data/test/generators/test_account_model_generator.rb +1 -36
  22. data/test/generators/test_admin_app_generator.rb +149 -18
  23. data/test/generators/test_admin_page_generator.rb +31 -8
  24. data/test/test_admin_application.rb +101 -0
  25. metadata +6 -9
  26. data/lib/padrino-admin/generators/templates/account/couchrest.rb.tt +0 -67
  27. data/lib/padrino-admin/generators/templates/account/datamapper.rb.tt +0 -61
  28. data/lib/padrino-admin/generators/templates/account/mongomapper.rb.tt +0 -47
  29. data/lib/padrino-admin/generators/templates/assets/javascripts/jquery-1.11.0.min.js +0 -4
@@ -5,7 +5,7 @@
5
5
 
6
6
  .base-icons
7
7
  .btn-group
8
- .btn.btn-primary{title: 'Adapters for datamapper, sequel, activerecord, minirecord, mongomapper, mongoid, couchrest'}
8
+ .btn.btn-primary{title: 'Adapters for activerecord, minirecord, sequel, mongoid, ohm, dynamoid'}
9
9
  = tag_icon('cogs fa-2x', 'Orm Agnostic')
10
10
  .btn.btn-success{title: 'User Authentication Support, User Authorization Management'}
11
11
  = tag_icon('group fa-2x', 'Authentication')
@@ -46,4 +46,4 @@
46
46
  %li= link_to tag_icon(:github, 'code'), 'https://github.com/padrino/padrino-framework', target: :_blank, class: 'footer-links-link'
47
47
  %li= link_to tag_icon(:twitter, 'twitter'), 'http://twitter.com/padrinorb', target: :_blank, class: 'footer-links-link'
48
48
 
49
- =javascript_include_tag 'jquery-1.11.0.min', (Padrino.env == :production ? 'bootstrap/bootstrap.min' : %w[bootstrap/affix bootstrap/alert bootstrap/button bootstrap/carousel bootstrap/collapse bootstrap/dropdown bootstrap/tooltip bootstrap/transition bootstrap/modal bootstrap/popover bootstrap/scrollspy bootstrap/tab]), :application
49
+ =javascript_include_tag 'jquery-3.7.1.min', (Padrino.env == :production ? 'bootstrap/bootstrap.min' : %w[bootstrap/affix bootstrap/alert bootstrap/button bootstrap/carousel bootstrap/collapse bootstrap/dropdown bootstrap/tooltip bootstrap/transition bootstrap/modal bootstrap/popover bootstrap/scrollspy bootstrap/tab]), :application
@@ -35,5 +35,5 @@
35
35
  .login-footer.modal-footer
36
36
  = submit_tag(pat('login.sign_in'), class: 'btn btn-primary pull-right')
37
37
 
38
- =javascript_include_tag 'jquery-1.11.0.min', (Padrino.env == :production ? 'bootstrap/bootstrap.min' : %w[bootstrap/affix bootstrap/alert bootstrap/button bootstrap/carousel bootstrap/collapse bootstrap/dropdown bootstrap/tooltip bootstrap/transition bootstrap/modal bootstrap/popover bootstrap/scrollspy bootstrap/tab]), :application
38
+ =javascript_include_tag 'jquery-3.7.1.min', (Padrino.env == :production ? 'bootstrap/bootstrap.min' : %w[bootstrap/affix bootstrap/alert bootstrap/button bootstrap/carousel bootstrap/collapse bootstrap/dropdown bootstrap/tooltip bootstrap/transition bootstrap/modal bootstrap/popover bootstrap/scrollspy bootstrap/tab]), :application
39
39
 
@@ -5,7 +5,7 @@ div class="base-text"
5
5
 
6
6
  div class="base-icons"
7
7
  div class="btn-group"
8
- div class="btn btn-primary" title="Adapters for datamapper, sequel, activerecord, minirecord, mongomapper, mongoid, couchrest" = tag_icon("cogs fa-2x", "Orm Agnostic")
8
+ div class="btn btn-primary" title="Adapters for activerecord, minirecord, sequel, mongoid, ohm, dynamoid" = tag_icon("cogs fa-2x", "Orm Agnostic")
9
9
  div class="btn btn-success" title="User Authentication Support, User Authorization Management" = tag_icon("group fa-2x", "Authentication")
10
10
  div class="btn btn-info" title="Erb, Haml, Slim Rendering Support" = tag_icon("tasks fa-2x", "Template Agnostic")
11
11
  div class="btn btn-warning" title="English, German, Russian, Danish, French, Brazilian and Italian localizations" = tag_icon("flag fa-2x", "Multi Language")
@@ -46,4 +46,4 @@ html lang='en'
46
46
  li = link_to tag_icon(:github, 'code'), 'https://github.com/padrino/padrino-framework', target: :_blank, class: 'footer-links-link'
47
47
  li = link_to tag_icon(:twitter, 'twitter'), 'http://twitter.com/padrinorb', target: :_blank, class: 'footer-links-link'
48
48
 
49
- = javascript_include_tag 'jquery-1.11.0.min', (Padrino.env == :production ? 'bootstrap/bootstrap.min' : %w[bootstrap/affix bootstrap/alert bootstrap/button bootstrap/carousel bootstrap/collapse bootstrap/dropdown bootstrap/tooltip bootstrap/transition bootstrap/modal bootstrap/popover bootstrap/scrollspy bootstrap/tab]), :application
49
+ = javascript_include_tag 'jquery-3.7.1.min', (Padrino.env == :production ? 'bootstrap/bootstrap.min' : %w[bootstrap/affix bootstrap/alert bootstrap/button bootstrap/carousel bootstrap/collapse bootstrap/dropdown bootstrap/tooltip bootstrap/transition bootstrap/modal bootstrap/popover bootstrap/scrollspy bootstrap/tab]), :application
@@ -34,4 +34,4 @@ html lang="en" xmlns="http://www.w3.org/1999/xhtml"
34
34
 
35
35
  div class="login-footer modal-footer"
36
36
  = submit_tag(pat('login.sign_in'), class: 'btn btn-primary pull-right')
37
- = javascript_include_tag 'jquery-1.11.0.min', (Padrino.env == :production ? 'bootstrap/bootstrap.min' : %w[bootstrap/affix bootstrap/alert bootstrap/button bootstrap/carousel bootstrap/collapse bootstrap/dropdown bootstrap/tooltip bootstrap/transition bootstrap/modal bootstrap/popover bootstrap/scrollspy bootstrap/tab]), :application
37
+ = javascript_include_tag 'jquery-3.7.1.min', (Padrino.env == :production ? 'bootstrap/bootstrap.min' : %w[bootstrap/affix bootstrap/alert bootstrap/button bootstrap/carousel bootstrap/collapse bootstrap/dropdown bootstrap/tooltip bootstrap/transition bootstrap/modal bootstrap/popover bootstrap/scrollspy bootstrap/tab]), :application
data/lib/padrino-admin.rb CHANGED
@@ -8,7 +8,7 @@ module Padrino
8
8
  ##
9
9
  # Padrino::Admin is beautiful Ajax Admin, with these features:
10
10
  #
11
- # Orm Agnostic:: Adapters for datamapper, activerecord, mongomapper, couchdb (now only: datamapper and activerecord), ohm
11
+ # Orm Agnostic:: Adapters for activerecord, sequel, mongoid, ohm, and more
12
12
  # Authentication:: Support for Account authentication, Account Permission management
13
13
  # Scaffold:: You can simply create a new "admin interface" simply providing a Model
14
14
  # Ajax Uploads:: You can upload file, manage them and attach them to any model in a quick and simple way (coming soon)
@@ -7,7 +7,7 @@ describe 'AccountModelGenerator' do
7
7
  end
8
8
 
9
9
  after do
10
- %(rm -rf #{@apptmp})
10
+ `rm -rf #{@apptmp}`
11
11
  end
12
12
 
13
13
  describe 'activerecord' do
@@ -34,18 +34,6 @@ describe 'AccountModelGenerator' do
34
34
  end
35
35
  end
36
36
 
37
- describe 'datamapper' do
38
- before do
39
- capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=datamapper') }
40
- capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
41
- @model = "#{@apptmp}/sample_project/models/account.rb"
42
- end
43
-
44
- it 'should include the datamapper resource' do
45
- assert_match_in_file(/include DataMapper::Resource/m, @model)
46
- end
47
- end
48
-
49
37
  describe 'mongoid' do
50
38
  before do
51
39
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=mongoid') }
@@ -58,18 +46,6 @@ describe 'AccountModelGenerator' do
58
46
  end
59
47
  end
60
48
 
61
- describe 'mongomapper' do
62
- before do
63
- capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=mongomapper') }
64
- capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
65
- @model = "#{@apptmp}/sample_project/models/account.rb"
66
- end
67
-
68
- it 'should include the mongomapper document' do
69
- assert_match_in_file(/include MongoMapper::Document/m, @model)
70
- end
71
- end
72
-
73
49
  describe 'ohm' do
74
50
  before do
75
51
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=ohm') }
@@ -94,15 +70,4 @@ describe 'AccountModelGenerator' do
94
70
  end
95
71
  end
96
72
 
97
- describe 'couchrest' do
98
- before do
99
- capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=couchrest') }
100
- capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
101
- @model = "#{@apptmp}/sample_project/models/account.rb"
102
- end
103
-
104
- it 'should be a couchrest model instance' do
105
- assert_match_in_file(/class Account < CouchRest::Model::Base/m, @model)
106
- end
107
- end
108
73
  end
@@ -1,4 +1,5 @@
1
1
  require_relative '../helper'
2
+ require 'open3'
2
3
 
3
4
  describe 'AdminAppGenerator' do
4
5
  before do
@@ -12,27 +13,100 @@ describe 'AdminAppGenerator' do
12
13
 
13
14
  describe 'the admin app generator' do
14
15
  before do
15
- # Account gets created by Datamapper's migration and then gets
16
- # rejected by model generator as already defined
17
16
  Object.send(:remove_const, :Account) if defined?(Account)
18
17
  end
19
18
 
20
19
  it 'should fail outside app root' do
21
- out, = capture_io { generate(:admin_app, "-r=#{@apptmp}") }
20
+ out, = capture_io { assert_equal 1, assert_raises(SystemExit) { generate(:admin_app, "-r=#{@apptmp}") }.status }
22
21
  assert_match(/not at the root/, out)
23
22
  assert_no_file_exists("#{@apptmp}/admin")
24
23
  end
25
24
 
26
25
  it "should fail if we don't specify an orm on the project" do
27
26
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-e=haml') }
28
- assert_raises(SystemExit) { @out, @err = capture_io { generate(:admin_app, "-r=#{@apptmp}/sample_project") } }
27
+ out, = capture_io { assert_equal 1, assert_raises(SystemExit) { generate(:admin_app, "-r=#{@apptmp}/sample_project") }.status }
28
+ assert_match(/Padrino only supports/, out)
29
+ end
30
+
31
+ it 'should fail with an unsupported renderer' do
32
+ capture_io { generate(:project, 'sample_project', "--root=#{@apptmp}", '-d=activerecord', '-e=haml') }
33
+ out, = capture_io { assert_equal 1, assert_raises(SystemExit) { generate(:admin_app, "-r=#{@apptmp}/sample_project", '-e=liquid') }.status }
34
+ assert_match(/for admin we only support/, out)
35
+ assert_no_file_exists("#{@apptmp}/sample_project/admin")
36
+ end
37
+
38
+ it 'should reuse an existing account model' do
39
+ capture_io { generate(:project, 'sample_project', "--root=#{@apptmp}", '-d=activerecord', '-e=haml') }
40
+ Object.const_set(:Account, Class.new)
41
+ begin
42
+ capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
43
+ rescue SystemExit => e
44
+ flunk "the admin generator exited with status #{e.status}"
45
+ end
46
+ assert_file_exists("#{@apptmp}/sample_project/admin/app.rb")
47
+ assert_match_in_file(/class Account < ActiveRecord::Base/, "#{@apptmp}/sample_project/models/account.rb")
48
+ assert_no_file_exists("#{@apptmp}/sample_project/db/migrate/001_create_accounts.rb")
49
+ ensure
50
+ Object.send(:remove_const, :Account) if defined?(Account)
51
+ end
52
+
53
+ it 'should generate the account model test with the project test_config path' do
54
+ capture_io { generate(:project, 'sample_project', '-e=haml', "--root=#{@apptmp}", '-d=activerecord', '-t=minitest') }
55
+ capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
56
+ assert_match_in_file %r{__dir__\}/\.\./test_config\.rb}, "#{@apptmp}/sample_project/test/models/account_test.rb"
57
+ end
58
+
59
+ it 'should generate seeds that read the account from piped input' do
60
+ capture_io { generate(:project, 'sample_project', "--root=#{@apptmp}", '-d=sequel', '-e=haml') }
61
+ capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
62
+ script = <<~RUBY
63
+ require 'thor'
64
+ # Thor's line editor can echo or buffer piped input, so without a terminal the
65
+ # seeds must not ask through it.
66
+ class NoAskShell < Thor::Base.shell
67
+ def ask(*)
68
+ raise 'shell.ask used without a terminal'
69
+ end
70
+ end
71
+
72
+ def shell
73
+ @shell ||= NoAskShell.new
74
+ end
75
+ class Account
76
+ def initialize(attributes)
77
+ @attributes = attributes
78
+ end
79
+
80
+ def valid?
81
+ true
82
+ end
83
+
84
+ def save
85
+ puts "SAVED \#{@attributes[:email]} \#{@attributes[:password] == 'secret123'}"
86
+ end
87
+ end
88
+ load 'db/seeds.rb'
89
+ RUBY
90
+ env = { 'BUNDLE_GEMFILE' => File.expand_path('../../../Gemfile', __dir__) }
91
+ out, err, status = Open3.capture3(env, RbConfig.ruby, '-rbundler/setup', '-e', script,
92
+ chdir: "#{@apptmp}/sample_project", stdin_data: "admin@example.com \nsecret123\r\n")
93
+ assert status.success?, err
94
+ assert_includes out.lines.map(&:strip), 'SAVED admin@example.com true'
95
+ refute_includes out, 'secret123'
29
96
  end
30
97
 
31
98
  it 'should store and apply session_secret' do
32
- capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=datamapper', '-e=haml') }
99
+ capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=sequel', '-e=haml') }
33
100
  assert_match_in_file(/set :session_secret, '[0-9A-z]*'/, "#{@apptmp}/sample_project/config/apps.rb")
34
101
  end
35
102
 
103
+ it 'should set SameSite=Lax on the admin session cookie' do
104
+ capture_io { generate(:project, 'sample_project', '-e=haml', "--root=#{@apptmp}", '-d=activerecord') }
105
+ capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
106
+ assert_match_in_file(/^ set :sessions, same_site: :lax$/, "#{@apptmp}/sample_project/admin/app.rb")
107
+ assert_no_match_in_file(/^\s*enable\s+:sessions/, "#{@apptmp}/sample_project/admin/app.rb")
108
+ end
109
+
36
110
  it 'should generate the admin app' do
37
111
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=activerecord') }
38
112
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
@@ -49,7 +123,7 @@ describe 'AdminAppGenerator' do
49
123
  assert_file_exists("#{@apptmp}/sample_project/public/admin/stylesheets/application.css")
50
124
  assert_file_exists("#{@apptmp}/sample_project/public/admin/stylesheets/bootstrap.css")
51
125
  assert_file_exists("#{@apptmp}/sample_project/public/admin/javascripts/application.js")
52
- assert_file_exists("#{@apptmp}/sample_project/public/admin/javascripts/jquery-1.11.0.min.js")
126
+ assert_file_exists("#{@apptmp}/sample_project/public/admin/javascripts/jquery-3.7.1.min.js")
53
127
  assert_file_exists("#{@apptmp}/sample_project/models/account.rb")
54
128
  assert_file_exists("#{@apptmp}/sample_project/db/seeds.rb")
55
129
  assert_file_exists("#{@apptmp}/sample_project/db/migrate/001_create_accounts.rb")
@@ -74,6 +148,17 @@ describe 'AdminAppGenerator' do
74
148
  assert_match_in_file(/Hello, custom generator/, "#{@apptmp}/sample_project/admin/views/layouts/application.slim")
75
149
  end
76
150
 
151
+ it 'should generate the account pages in an admin app named with a plural' do
152
+ capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=sequel') }
153
+ begin
154
+ capture_io { generate(:admin_app, '-a=admins', "--root=#{@apptmp}/sample_project") }
155
+ rescue SystemExit => e
156
+ flunk "the admin generator exited with status #{e.status}"
157
+ end
158
+ assert_file_exists("#{@apptmp}/sample_project/admins/controllers/accounts.rb")
159
+ assert_no_dir_exists("#{@apptmp}/sample_project/admin")
160
+ end
161
+
77
162
  it 'should generate the admin app under a different folder' do
78
163
  # TODO: FIXME Implement option --admin_root or something. See https://github.com/padrino/padrino-framework/issues/854#issuecomment-14749356
79
164
  skip
@@ -124,6 +209,49 @@ describe 'AdminAppGenerator' do
124
209
  assert_file_exists("#{@apptmp}/sample_project/admin/views/layouts/application.slim")
125
210
  assert_file_exists("#{@apptmp}/sample_project/admin/views/sessions/new.slim")
126
211
  end
212
+
213
+ %w[haml erb slim].each do |engine|
214
+ it "should serve every script the #{engine} admin pages include" do
215
+ skip 'the sqlite3 gem is only bundled on MRI' if RUBY_ENGINE == 'jruby'
216
+ capture_io { generate(:project, 'sample_project', "--root=#{@apptmp}", '-d=sequel', "-e=#{engine}") }
217
+ capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
218
+ script = <<~'RUBY'
219
+ ENV['RACK_ENV'] = 'test'
220
+ %w[sequel haml slim erubi nokogiri rack/test].each { |lib| require lib }
221
+ Sequel.extension :migration
222
+ Sequel.sqlite('db/sample_project_test.db') { |db| Sequel::Migrator.run(db, 'db/migrate') }
223
+ require File.expand_path('config/boot')
224
+ account_id = Account.dataset.insert(email: 'admin@example.com', role: 'admin', crypted_password: 'unused')
225
+
226
+ browser = Rack::Test::Session.new(Padrino.application)
227
+ check = lambda do |page|
228
+ browser.get(page)
229
+ puts "PAGE #{page} #{browser.last_response.status}"
230
+ srcs = Nokogiri::HTML(browser.last_response.body).css('script[src]').map { |tag| tag['src'] }
231
+ srcs.select { |src| src.start_with?('/') }.each do |src|
232
+ puts "SCRIPT #{page} #{src} #{browser.get(src).status}"
233
+ end
234
+ end
235
+ check.call('/admin/sessions/new')
236
+ browser.env('rack.session', SampleProject::Admin.session_id => account_id)
237
+ check.call('/admin/')
238
+ check.call('/admin/accounts')
239
+ RUBY
240
+ env = { 'BUNDLE_GEMFILE' => File.expand_path('../../../Gemfile', __dir__) }
241
+ out, err, status = Open3.capture3(env, RbConfig.ruby, '-rbundler/setup', '-e', script, chdir: "#{@apptmp}/sample_project")
242
+ assert status.success?, err
243
+ pages = out.lines.grep(/\APAGE /).map(&:split)
244
+ scripts = out.lines.grep(/\ASCRIPT /).map(&:split)
245
+ statuses = pages.map { |line| line[1, 2] }
246
+ assert_equal [['/admin/sessions/new', '200'], ['/admin/', '200'], ['/admin/accounts', '200']], statuses
247
+ pages.each do |(_, page)|
248
+ refute_empty scripts.select { |line| line[1] == page }, "#{page} includes no local scripts"
249
+ end
250
+ scripts.each do |(_, page, src, code)|
251
+ assert_equal '200', code, "#{page} includes #{src}, which is not served"
252
+ end
253
+ end
254
+ end
127
255
  end
128
256
 
129
257
  it 'should correctly generate a new padrino admin application with a custom model' do
@@ -192,24 +320,27 @@ describe 'AdminAppGenerator' do
192
320
  assert_match_in_file(/ use ConnectionPoolManagement/m, "#{@apptmp}/sample_project/admin/app.rb")
193
321
  end
194
322
 
195
- it 'should not add it for #datamapper' do
196
- capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=datamapper', '-e=haml') }
197
- capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
198
- assert_no_match_in_file(/ use ConnectionPoolManagement/m, "#{@apptmp}/sample_project/admin/app.rb")
199
- end
200
323
  end
201
324
 
202
- describe 'datamapper middleware' do
203
- it 'should add it for #datamapper' do
204
- capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=datamapper', '-e=haml') }
325
+ describe 'with the vite script component' do
326
+ def assert_activesupport_listed_once(gemfile)
327
+ Bundler.ui.expects(:warn).never
328
+ dsl = Bundler::Dsl.new
329
+ dsl.eval_gemfile(gemfile)
330
+ assert_equal(1, dsl.dependencies.count { |dep| dep.name == 'activesupport' })
331
+ end
332
+
333
+ it 'should list activesupport once when admin is added after vite' do
334
+ capture_io { generate(:project, 'sample_project', '-e=haml', "--root=#{@apptmp}", '-d=sequel', '--script=vite') }
205
335
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
206
- assert_match_in_file(/ use IdentityMap/m, "#{@apptmp}/sample_project/admin/app.rb")
336
+ assert_activesupport_listed_once("#{@apptmp}/sample_project/Gemfile")
207
337
  end
208
338
 
209
- it 'should not add it for #activerecord' do
210
- capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=activerecord', '-e=haml') }
339
+ it 'should list activesupport once when vite is added after admin' do
340
+ capture_io { generate(:project, 'sample_project', '-e=haml', "--root=#{@apptmp}", '-d=sequel') }
211
341
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
212
- assert_no_match_in_file(/ use IdentityMap/m, "#{@apptmp}/sample_project/admin/app.rb")
342
+ capture_io { generate(:component, '--script=vite', "-r=#{@apptmp}/sample_project") }
343
+ assert_activesupport_listed_once("#{@apptmp}/sample_project/Gemfile")
213
344
  end
214
345
  end
215
346
 
@@ -13,21 +13,50 @@ describe 'AdminPageGenerator' do
13
13
 
14
14
  describe 'the admin page generator' do
15
15
  it 'should fail outside app root' do
16
- out, = capture_io { generate(:admin_page, 'foo', "-r=#{@apptmp}/sample_project") }
16
+ out, = capture_io { assert_equal 1, assert_raises(SystemExit) { generate(:admin_page, 'foo', "-r=#{@apptmp}/sample_project") }.status }
17
17
  assert_match(/not at the root/, out)
18
18
  assert_no_file_exists("#{@apptmp}/admin")
19
19
  end
20
20
 
21
+ it 'should fail when the admin app does not exist' do
22
+ capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=sequel') }
23
+ out, = capture_io { assert_equal 1, assert_raises(SystemExit) { generate(:admin_page, 'friend', "-r=#{@apptmp}/sample_project") }.status }
24
+ assert_match(/Unable to locate 'Admin' application/, out)
25
+ assert_no_dir_exists("#{@apptmp}/sample_project/admin")
26
+ end
27
+
21
28
  it 'should fail without an existent model' do
22
29
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=activerecord') }
23
30
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
24
31
  assert_raises(Padrino::Admin::Generators::OrmError) { generate(:admin_page, 'foo', "-r=#{@apptmp}/sample_project") }
25
32
  end
26
33
 
34
+ it 'should fail when the model does not match the project orm' do
35
+ capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=activerecord') }
36
+ capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
37
+ error = assert_raises(Padrino::Admin::Generators::OrmError) { capture_io { generate(:admin_page, 'friend', "-r=#{@apptmp}/sample_project") } }
38
+ assert_match(/Friend.*activerecord/m, error.message)
39
+ end
40
+
41
+ it 'should destroy a page even when the model does not match the project orm' do
42
+ capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=activerecord') }
43
+ capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
44
+ FileUtils.mkdir_p("#{@apptmp}/sample_project/admin/views/friends")
45
+ capture_io { generate(:admin_page, 'friend', "-r=#{@apptmp}/sample_project", '-d') }
46
+ assert_no_dir_exists("#{@apptmp}/sample_project/admin/views/friends")
47
+ end
48
+
49
+ it 'should fail when the project has no orm and the model is not activerecord' do
50
+ capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=sequel') }
51
+ capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
52
+ components = "#{@apptmp}/sample_project/.components"
53
+ File.write(components, File.read(components).sub(/^:orm:.*\n/, ''))
54
+ assert_raises(Padrino::Admin::Generators::OrmError) { capture_io { generate(:admin_page, 'friend', "-r=#{@apptmp}/sample_project") } }
55
+ end
56
+
27
57
  it 'should correctly generate a new page' do
28
58
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=sequel', '-e=haml') }
29
59
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
30
- capture_io { generate(:model, 'friend', 'name:string', 'age:integer', 'email:string', "--root=#{@apptmp}/sample_project") }
31
60
  capture_io { generate(:admin_page, 'friend', "--root=#{@apptmp}/sample_project") }
32
61
  assert_file_exists "#{@apptmp}/sample_project/admin/controllers/friends.rb"
33
62
  assert_match_in_file 'SampleProject::Admin.controllers :friends do', "#{@apptmp}/sample_project/admin/controllers/friends.rb"
@@ -41,7 +70,6 @@ describe 'AdminPageGenerator' do
41
70
  custom_template_path = "#{@apptmp}/sample_project/generators/templates/haml/page/"
42
71
  `mkdir -p #{custom_template_path} && echo "%h1= 'Hello, custom generator' " > #{custom_template_path}index.haml.tt`
43
72
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
44
- capture_io { generate(:model, 'friend', 'name:string', 'age:integer', 'email:string', "--root=#{@apptmp}/sample_project") }
45
73
  capture_io { generate(:admin_page, 'friend', "--root=#{@apptmp}/sample_project") }
46
74
  assert_match_in_file(/Hello, custom generator/, "#{@apptmp}/sample_project/admin/views/friends/index.haml")
47
75
  end
@@ -50,7 +78,6 @@ describe 'AdminPageGenerator' do
50
78
  it 'should correctly generate a new page with haml' do
51
79
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=sequel', '-e=haml') }
52
80
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
53
- capture_io { generate(:model, 'friend', 'name:string', 'age:integer', 'email:string', "--root=#{@apptmp}/sample_project") }
54
81
  capture_io { generate(:admin_page, 'friend', "--root=#{@apptmp}/sample_project") }
55
82
  assert_file_exists "#{@apptmp}/sample_project/admin/views/friends/_form.haml"
56
83
  assert_file_exists "#{@apptmp}/sample_project/admin/views/friends/edit.haml"
@@ -66,7 +93,6 @@ describe 'AdminPageGenerator' do
66
93
  it 'should correctly generate a new page with erb' do
67
94
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=sequel', '-e=erb') }
68
95
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
69
- capture_io { generate(:model, 'friend', 'name:string', 'age:integer', 'email:string', "--root=#{@apptmp}/sample_project") }
70
96
  capture_io { generate(:admin_page, 'friend', "--root=#{@apptmp}/sample_project") }
71
97
  assert_file_exists "#{@apptmp}/sample_project/admin/views/friends/_form.erb"
72
98
  assert_file_exists "#{@apptmp}/sample_project/admin/views/friends/edit.erb"
@@ -82,7 +108,6 @@ describe 'AdminPageGenerator' do
82
108
  it 'should correctly generate a new page with slim' do
83
109
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=sequel', '-e=slim') }
84
110
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
85
- capture_io { generate(:model, 'friend', 'name:string', 'age:integer', 'email:string', "--root=#{@apptmp}/sample_project") }
86
111
  capture_io { generate(:admin_page, 'friend', "--root=#{@apptmp}/sample_project") }
87
112
  assert_file_exists "#{@apptmp}/sample_project/admin/views/friends/_form.slim"
88
113
  assert_file_exists "#{@apptmp}/sample_project/admin/views/friends/edit.slim"
@@ -99,8 +124,6 @@ describe 'AdminPageGenerator' do
99
124
  it 'should correctly generate a new padrino admin application with multiple models at the same time' do
100
125
  capture_io { generate(:project, 'sample_project', '-e=slim', "--root=#{@apptmp}", '-d=sequel', '-e=haml') }
101
126
  capture_io { generate(:admin_app, "--root=#{@apptmp}/sample_project") }
102
- capture_io { generate(:model, 'friend', 'name:string', 'age:integer', 'email:string', "-root=#{@apptmp}/sample_project") }
103
- capture_io { generate(:model, 'page', 'name:string', 'body:string', "-root=#{@apptmp}/sample_project") }
104
127
  capture_io { generate(:admin_page, 'friend', 'page', "--root=#{@apptmp}/sample_project") }
105
128
  # For Friend
106
129
  assert_file_exists "#{@apptmp}/sample_project/admin/controllers/friends.rb"
@@ -163,6 +163,100 @@ describe 'AdminApplication' do
163
163
  assert_equal 'posts', body
164
164
  end
165
165
 
166
+ it 'should match paths literally without regex metacharacter interpretation' do
167
+ mock_app do
168
+ register Padrino::Admin::AccessControl
169
+ enable :sessions
170
+
171
+ access_control.roles_for :any do |role|
172
+ role.protect '/'
173
+ role.allow '/accounts.json'
174
+ end
175
+ end
176
+
177
+ # Without authentication, /accounts.json should be allowed
178
+ assert @app.access_control.allowed?(nil, '/accounts.json')
179
+ # /accountsXjson must NOT match - the dot in .json is literal, not a regex wildcard
180
+ refute @app.access_control.allowed?(nil, '/accountsXjson')
181
+ end
182
+
183
+ it 'should not allow paths that merely start with an allowed path' do
184
+ mock_app do
185
+ register Padrino::Admin::AccessControl
186
+ enable :sessions
187
+
188
+ access_control.roles_for :any do |role|
189
+ role.protect '/'
190
+ role.allow '/sessions'
191
+ end
192
+
193
+ access_control.roles_for :editor do |role|
194
+ role.project_module :posts, '/posts'
195
+ end
196
+
197
+ access_control.roles_for :admin do |role|
198
+ role.project_module :post_admin, '/posts_admin'
199
+ end
200
+ end
201
+
202
+ assert @app.access_control.allowed?(nil, '/sessions')
203
+ assert @app.access_control.allowed?(nil, '/sessions/new')
204
+ assert @app.access_control.allowed?(nil, '/sessions.json')
205
+ refute @app.access_control.allowed?(nil, '/sessions_admin')
206
+ refute @app.access_control.allowed?(nil, '/sessionsX')
207
+
208
+ # An editor's '/posts' must not reach the admin-only '/posts_admin' module.
209
+ assert @app.access_control.allowed?(Account.editor, '/posts')
210
+ assert @app.access_control.allowed?(Account.editor, '/posts/1/edit')
211
+ refute @app.access_control.allowed?(Account.editor, '/posts_admin')
212
+ assert @app.access_control.allowed?(Account.admin, '/posts_admin')
213
+
214
+ # Paths are compared after percent-decoding.
215
+ refute @app.access_control.allowed?(Account.editor, '/%70osts_admin')
216
+ assert @app.access_control.allowed?(Account.editor, '/%70osts')
217
+ end
218
+
219
+ it 'should allow decoded non-ASCII paths and prefixes ending in a dot' do
220
+ mock_app do
221
+ register Padrino::Admin::AccessControl
222
+ enable :sessions
223
+
224
+ access_control.roles_for :any do |role|
225
+ role.protect '/'
226
+ role.allow '/café'
227
+ role.allow '/v1.'
228
+ end
229
+ end
230
+
231
+ assert @app.access_control.allowed?(nil, '/caf%C3%A9'.b)
232
+ assert @app.access_control.allowed?(nil, '/café')
233
+ assert @app.access_control.allowed?(nil, '/v1.json')
234
+ refute @app.access_control.allowed?(nil, '/v2.json')
235
+ end
236
+
237
+ it 'should match Regexp paths at the start of the path' do
238
+ mock_app do
239
+ register Padrino::Admin::AccessControl
240
+ enable :sessions
241
+
242
+ access_control.roles_for :any do |role|
243
+ role.protect %r{\A/secret}
244
+ role.allow %r{\A/reports/\d+\z}
245
+ end
246
+
247
+ access_control.roles_for :admin do |role|
248
+ role.allow %r{\A/stats}
249
+ role.project_module :posts, '/posts'
250
+ end
251
+ end
252
+
253
+ assert @app.access_control.allowed?(nil, '/reports/1')
254
+ refute @app.access_control.allowed?(nil, '/secret/x')
255
+ assert @app.access_control.allowed?(Account.admin, '/stats/today')
256
+ assert @app.access_control.allowed?(Account.admin, '/posts')
257
+ refute @app.access_control.allowed?(Account.editor, '/stats')
258
+ end
259
+
166
260
  it 'should emulate an ecommerce app' do
167
261
  mock_app do
168
262
  register Padrino::Admin::AccessControl
@@ -282,4 +376,11 @@ describe 'AdminApplication' do
282
376
  assert_equal '/foo', app1.access_control.project_modules(:any).first.path
283
377
  assert_equal '/bar', app2.access_control.project_modules(:any).first.path
284
378
  end
379
+
380
+ it 'should raise AccessControlError for invalid roles' do
381
+ access_control = Padrino::Admin::AccessControl::Base.new
382
+ error = assert_raises(Padrino::Admin::AccessControlError) { access_control.roles_for('admin') }
383
+ assert_match(/"admin"/, error.message)
384
+ assert_raises(Padrino::Admin::AccessControlError) { access_control.roles_for }
385
+ end
285
386
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: padrino-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.2
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Padrino Team
@@ -19,28 +19,28 @@ dependencies:
19
19
  requirements:
20
20
  - - '='
21
21
  - !ruby/object:Gem::Version
22
- version: 0.16.2
22
+ version: 0.17.0
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.16.2
29
+ version: 0.17.0
30
30
  - !ruby/object:Gem::Dependency
31
31
  name: padrino-helpers
32
32
  requirement: !ruby/object:Gem::Requirement
33
33
  requirements:
34
34
  - - '='
35
35
  - !ruby/object:Gem::Version
36
- version: 0.16.2
36
+ version: 0.17.0
37
37
  type: :runtime
38
38
  prerelease: false
39
39
  version_requirements: !ruby/object:Gem::Requirement
40
40
  requirements:
41
41
  - - '='
42
42
  - !ruby/object:Gem::Version
43
- version: 0.16.2
43
+ version: 0.17.0
44
44
  - !ruby/object:Gem::Dependency
45
45
  name: therubyracer
46
46
  requirement: !ruby/object:Gem::Requirement
@@ -143,12 +143,9 @@ files:
143
143
  - lib/padrino-admin/generators/admin_page.rb
144
144
  - lib/padrino-admin/generators/orm.rb
145
145
  - lib/padrino-admin/generators/templates/account/activerecord.rb.tt
146
- - lib/padrino-admin/generators/templates/account/couchrest.rb.tt
147
- - lib/padrino-admin/generators/templates/account/datamapper.rb.tt
148
146
  - lib/padrino-admin/generators/templates/account/dynamoid.rb.tt
149
147
  - lib/padrino-admin/generators/templates/account/minirecord.rb.tt
150
148
  - lib/padrino-admin/generators/templates/account/mongoid.rb.tt
151
- - lib/padrino-admin/generators/templates/account/mongomapper.rb.tt
152
149
  - lib/padrino-admin/generators/templates/account/ohm.rb.tt
153
150
  - lib/padrino-admin/generators/templates/account/seeds.rb.tt
154
151
  - lib/padrino-admin/generators/templates/account/sequel.rb.tt
@@ -176,7 +173,7 @@ files:
176
173
  - lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/tab.js
177
174
  - lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/tooltip.js
178
175
  - lib/padrino-admin/generators/templates/assets/javascripts/bootstrap/transition.js
179
- - lib/padrino-admin/generators/templates/assets/javascripts/jquery-1.11.0.min.js
176
+ - lib/padrino-admin/generators/templates/assets/javascripts/jquery-3.7.1.min.js
180
177
  - lib/padrino-admin/generators/templates/assets/stylesheets/application.css
181
178
  - lib/padrino-admin/generators/templates/assets/stylesheets/bootstrap.css
182
179
  - lib/padrino-admin/generators/templates/assets/stylesheets/fonts/FontAwesome.otf