trusty-cms 7.1.3 → 7.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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +72 -68
  3. data/lib/string_extensions/string_extensions.rb +4 -2
  4. data/lib/trusty_cms/version.rb +1 -1
  5. data/spec/controllers/admin/assets_controller_spec.rb +124 -0
  6. data/spec/controllers/admin/changes_controller_spec.rb +72 -0
  7. data/spec/controllers/admin/configuration_controller_spec.rb +49 -0
  8. data/spec/controllers/admin/page_attachments_controller_spec.rb +54 -0
  9. data/spec/controllers/admin/pages_controller_spec.rb +162 -0
  10. data/spec/controllers/admin/preferences_controller_spec.rb +44 -0
  11. data/spec/controllers/admin/security_controller_spec.rb +85 -0
  12. data/spec/controllers/admin/sessions_controller_spec.rb +56 -0
  13. data/spec/controllers/admin/two_factor_controller_spec.rb +63 -0
  14. data/spec/controllers/admin/users_controller_spec.rb +132 -0
  15. data/spec/controllers/page_status_controller_spec.rb +66 -0
  16. data/spec/dummy/bin/rails +3 -3
  17. data/spec/dummy/bin/rake +2 -2
  18. data/spec/dummy/bin/setup +21 -13
  19. data/spec/dummy/config/boot.rb +1 -1
  20. data/spec/dummy/config/cable.yml +10 -0
  21. data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
  22. data/spec/dummy/config/initializers/filter_parameter_logging.rb +6 -2
  23. data/spec/dummy/config/initializers/inflections.rb +4 -4
  24. data/spec/dummy/config/initializers/new_framework_defaults_7_2.rb +70 -0
  25. data/spec/dummy/config/initializers/permissions_policy.rb +13 -0
  26. data/spec/dummy/config/puma.rb +34 -0
  27. data/spec/dummy/config/storage.yml +28 -1
  28. data/spec/dummy/db/migrate/20260729201444_add_service_name_to_active_storage_blobs.active_storage.rb +22 -0
  29. data/spec/dummy/db/migrate/20260729201445_create_active_storage_variant_records.active_storage.rb +27 -0
  30. data/spec/dummy/db/migrate/20260729201446_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb +8 -0
  31. data/spec/dummy/public/404.html +6 -6
  32. data/spec/dummy/public/406-unsupported-browser.html +66 -0
  33. data/spec/dummy/public/422.html +6 -6
  34. data/spec/dummy/public/500.html +6 -6
  35. data/spec/dummy/public/icon.png +0 -0
  36. data/spec/dummy/public/icon.svg +3 -0
  37. data/spec/dummy/public/robots.txt +1 -0
  38. data/spec/helpers/admin/configuration_helper_spec.rb +62 -0
  39. data/spec/helpers/admin/node_helper_spec.rb +132 -0
  40. data/spec/helpers/admin/pages_helper_spec.rb +84 -0
  41. data/spec/helpers/application_helper_spec.rb +115 -0
  42. data/spec/lib/string_extensions_spec.rb +41 -0
  43. data/spec/support/active_record_encryption.rb +10 -0
  44. data/spec/support/asset_type_registry.rb +29 -0
  45. data/trusty_cms.gemspec +1 -1
  46. metadata +68 -8
  47. data/spec/controllers/assets_controller.rb +0 -66
@@ -0,0 +1,162 @@
1
+ require 'spec_helper'
2
+
3
+ # Exercises Admin::PagesController — the most heavily customized ResourceController
4
+ # subclass (search, restore, per-site edit guards, meta-row setup, ordering).
5
+ # Controller specs (views not rendered) let us drive the actions and their many
6
+ # private helpers without the asset-pipeline-heavy admin HTML layout. This is the
7
+ # highest-blast-radius controller in the app, so keep it green across the Rails 8 bump.
8
+ RSpec.describe Admin::PagesController, type: :controller do
9
+ routes { TrustyCms::Application.routes }
10
+
11
+ let(:admin) { create(:admin) }
12
+ let!(:home) { create(:home, title: 'Home') }
13
+
14
+ before do
15
+ allow(controller).to receive(:authenticate_user!).and_return(true)
16
+ allow(controller).to receive(:current_user).and_return(admin)
17
+ # #current_site / Page.current_site / Page.homepage come from the multi-site
18
+ # extension, which isn't loaded in the dummy app; stub the touch points.
19
+ allow(Page).to receive(:current_site).and_return(nil)
20
+ allow(Page).to receive(:homepage).and_return(home)
21
+ end
22
+
23
+ describe 'GET #index' do
24
+ it 'succeeds and sets up the site/homepage/search' do
25
+ get :index
26
+ expect(response).to have_http_status(:ok)
27
+ expect(controller.instance_variable_get(:@homepage)).to eq(home)
28
+ end
29
+ end
30
+
31
+ describe 'GET #new' do
32
+ it 'builds a new page with defaults' do
33
+ get :new
34
+ expect(response).to have_http_status(:ok)
35
+ page = controller.instance_variable_get(:@page)
36
+ expect(page).to be_a(Page).and be_new_record
37
+ end
38
+
39
+ it 'roots a top-level page at "/" when no parent is given' do
40
+ get :new
41
+ expect(controller.instance_variable_get(:@page).slug).to eq('/')
42
+ end
43
+
44
+ it 'sets the parent_id from page_id' do
45
+ get :new, params: { page_id: home.id }
46
+ expect(controller.instance_variable_get(:@page).parent_id).to eq(home.id)
47
+ end
48
+ end
49
+
50
+ describe 'GET #edit' do
51
+ context 'when the page belongs to a site the admin can access' do
52
+ let(:site) { create(:site) }
53
+ let(:page) { create(:page, title: 'Editable', parent: home, site_id: site.id) }
54
+
55
+ before { admin.sites << site }
56
+
57
+ it 'succeeds and loads edit assigns' do
58
+ get :edit, params: { id: page.id }
59
+
60
+ expect(response).to have_http_status(:ok)
61
+ expect(controller.instance_variable_get(:@page)).to eq(page)
62
+ expect(controller.instance_variable_get(:@page_url)).to be_present
63
+ end
64
+ end
65
+
66
+ context 'when the page belongs to a site the admin cannot access' do
67
+ let(:site) { create(:site) }
68
+ let(:page) { create(:page, title: 'Forbidden', parent: home, site_id: site.id) }
69
+
70
+ it 'redirects back to the pages index' do
71
+ get :edit, params: { id: page.id }
72
+ expect(response).to redirect_to(admin_pages_url)
73
+ end
74
+ end
75
+ end
76
+
77
+ describe 'POST #create' do
78
+ it 'creates a page and redirects' do
79
+ expect {
80
+ post :create, params: { page: { title: 'Fresh', slug: 'fresh', breadcrumb: 'Fresh', parent_id: home.id } }
81
+ }.to change(Page, :count).by(1)
82
+
83
+ expect(response).to be_redirect
84
+ end
85
+
86
+ it 're-renders new via the rescue_from validation_error handler when invalid' do
87
+ expect {
88
+ post :create, params: { page: { title: '', slug: '', breadcrumb: '', parent_id: home.id } }
89
+ }.not_to change(Page, :count)
90
+
91
+ expect(flash[:error]).to be_present
92
+ end
93
+ end
94
+
95
+ describe 'PUT #update' do
96
+ it 'updates the page and redirects' do
97
+ page = create(:page, title: 'Before', parent: home)
98
+
99
+ put :update, params: { id: page.id, page: { title: 'After' } }
100
+
101
+ expect(response).to be_redirect
102
+ expect(page.reload.title).to eq('After')
103
+ end
104
+ end
105
+
106
+ describe 'DELETE #destroy' do
107
+ it 'destroys the page and counts descendants' do
108
+ page = create(:page, title: 'Doomed', parent: home)
109
+
110
+ expect {
111
+ delete :destroy, params: { id: page.id }
112
+ }.to change(Page, :count).by(-1)
113
+
114
+ expect(response).to be_redirect
115
+ end
116
+ end
117
+
118
+ describe 'GET #search' do
119
+ it 'returns matching pages when a query is present' do
120
+ create(:page, title: 'Findable', slug: 'findable', parent: home)
121
+
122
+ get :search, params: { site_id: nil, search: { query: 'Findable' } }
123
+
124
+ expect(response).to have_http_status(:ok)
125
+ titles = controller.instance_variable_get(:@pages).map(&:title)
126
+ expect(titles).to include('Findable')
127
+ end
128
+
129
+ it 'does not run a query when none is given' do
130
+ get :search, params: { site_id: '' }
131
+ expect(controller.instance_variable_get(:@pages)).to be_nil
132
+ end
133
+ end
134
+
135
+ describe 'POST #save_table_position' do
136
+ it 'saves the new order and returns ok' do
137
+ allow(Page).to receive(:save_order)
138
+ post :save_table_position, params: { new_position: %w[1 2 3] }
139
+ expect(response).to have_http_status(:ok)
140
+ expect(Page).to have_received(:save_order).with(%w[1 2 3])
141
+ end
142
+ end
143
+
144
+ describe 'PUT #restore' do
145
+ # NOTE: latent bug / Rails 8 upgrade blocker — restore_page_version calls
146
+ # PaperTrail's `reify`, which YAML-loads the stored version. Under Psych 4+
147
+ # (psych 5.4 is pinned) safe-loading rejects ActiveSupport::TimeWithZone
148
+ # because no permitted classes are configured, so restoring any page that has
149
+ # a timestamp in its versioned state raises Psych::DisallowedClass. Page
150
+ # restore is effectively broken until paper_trail's serializer is configured
151
+ # with permitted classes (e.g. via ActiveRecord::Base.yaml_column_permitted_classes
152
+ # or a custom serializer). Characterizing current behavior.
153
+ it 'currently fails to reify a versioned page (Psych safe-load blocker)' do
154
+ page = create(:page, title: 'V1', parent: home)
155
+ PaperTrail.request(whodunnit: admin.id.to_s) { page.update!(title: 'V2') }
156
+
157
+ expect {
158
+ put :restore, params: { id: page.id, version_index: 1 }
159
+ }.to raise_error(Psych::DisallowedClass, /TimeWithZone/)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ # Exercises Admin::PreferencesController, which lets the current user edit their
4
+ # own profile/preferences. Simple ApplicationController subclass; covers the
5
+ # show/edit render paths and the valid/invalid update branches.
6
+ RSpec.describe Admin::PreferencesController, type: :controller do
7
+ routes { TrustyCms::Application.routes }
8
+
9
+ let(:admin) { create(:admin) }
10
+
11
+ before do
12
+ allow(controller).to receive(:authenticate_user!).and_return(true)
13
+ allow(controller).to receive(:current_user).and_return(admin)
14
+ end
15
+
16
+ describe 'GET #show' do
17
+ it 'succeeds' do
18
+ get :show
19
+ expect(response).to have_http_status(:ok)
20
+ end
21
+ end
22
+
23
+ describe 'GET #edit' do
24
+ it 'succeeds' do
25
+ get :edit
26
+ expect(response).to have_http_status(:ok)
27
+ end
28
+ end
29
+
30
+ describe 'PUT #update' do
31
+ it 'updates the current user and redirects to configuration' do
32
+ put :update, params: { user: { first_name: 'Preferred' } }
33
+
34
+ expect(response).to redirect_to(admin_configuration_path)
35
+ expect(admin.reload.first_name).to eq('Preferred')
36
+ end
37
+
38
+ it 're-renders edit with an error when the update is invalid' do
39
+ put :update, params: { user: { email: 'not-an-email' } }
40
+
41
+ expect(flash[:error]).to be_present
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,85 @@
1
+ require 'spec_helper'
2
+
3
+ # Exercises Admin::SecurityController — password changes and TOTP two-factor
4
+ # enable/disable/verify for the current user. Touches devise-two-factor and QR
5
+ # provisioning, both plausible upgrade casualties.
6
+ RSpec.describe Admin::SecurityController, type: :controller do
7
+ routes { TrustyCms::Application.routes }
8
+
9
+ let(:user) { create(:admin) }
10
+
11
+ before do
12
+ allow(controller).to receive(:authenticate_user!).and_return(true)
13
+ allow(controller).to receive(:current_user).and_return(user)
14
+ # sign_out/sign_in need Warden wiring we don't set up here; the auth side
15
+ # effect isn't what these specs are checking.
16
+ allow(controller).to receive(:sign_out)
17
+ end
18
+
19
+ describe 'GET #show' do
20
+ it 'generates an OTP secret and QR provisioning data' do
21
+ expect(user.otp_secret).to be_blank
22
+
23
+ get :show
24
+
25
+ expect(response).to have_http_status(:ok)
26
+ expect(user.reload.otp_secret).to be_present
27
+ expect(controller.instance_variable_get(:@qr_png_data)).to start_with('data:image/png')
28
+ end
29
+ end
30
+
31
+ describe 'GET #edit' do
32
+ it 'succeeds' do
33
+ get :edit
34
+ expect(response).to have_http_status(:ok)
35
+ end
36
+ end
37
+
38
+ describe 'PUT #update' do
39
+ it 'updates the password, signs out, and redirects to sign-in' do
40
+ put :update, params: { user: { password: 'NewComplex1!', password_confirmation: 'NewComplex1!' } }
41
+
42
+ expect(controller).to have_received(:sign_out).with(user)
43
+ expect(response).to redirect_to(new_user_session_path)
44
+ end
45
+
46
+ it 're-renders edit when the password change is invalid' do
47
+ put :update, params: { user: { password: 'x', password_confirmation: 'y' } }
48
+
49
+ expect(flash[:error]).to be_present
50
+ end
51
+ end
52
+
53
+ describe 'POST #verify_two_factor' do
54
+ it 'enables 2FA when the OTP is valid' do
55
+ allow(user).to receive(:validate_and_consume_otp!).with('123456').and_return(true)
56
+
57
+ post :verify_two_factor, params: { otp_attempt: '123456' }
58
+
59
+ expect(user.reload.otp_required_for_login).to be(true)
60
+ expect(response).to redirect_to(admin_security_path)
61
+ end
62
+
63
+ it 'reports an error when the OTP is invalid' do
64
+ allow(user).to receive(:validate_and_consume_otp!).and_return(false)
65
+
66
+ post :verify_two_factor, params: { otp_attempt: 'bad' }
67
+
68
+ expect(flash[:error]).to be_present
69
+ expect(response).to redirect_to(admin_security_path)
70
+ end
71
+ end
72
+
73
+ describe 'POST #disable_two_factor' do
74
+ it 'clears the OTP settings and redirects' do
75
+ user.update!(otp_required_for_login: true, otp_secret: User.generate_otp_secret)
76
+
77
+ post :disable_two_factor
78
+
79
+ user.reload
80
+ expect(user.otp_required_for_login).to be(false)
81
+ expect(user.otp_secret).to be_nil
82
+ expect(response).to redirect_to(admin_security_path)
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ # Exercises Admin::SessionsController#create — the custom login flow that
4
+ # branches into the two-factor handoff for OTP-enabled users. Overriding a
5
+ # Devise controller is exactly the sort of thing that can break across a Devise
6
+ # major, so pin the three branches (2FA, plain sign-in, bad credentials).
7
+ RSpec.describe Admin::SessionsController, type: :controller do
8
+ routes { TrustyCms::Application.routes }
9
+ include Devise::Test::ControllerHelpers
10
+
11
+ let(:password) { 'ComplexPass1!' }
12
+
13
+ before do
14
+ request.env['devise.mapping'] = Devise.mappings[:user]
15
+ # sign_in needs Warden wiring that isn't the focus here.
16
+ allow(controller).to receive(:sign_in)
17
+ end
18
+
19
+ describe 'POST #create' do
20
+ it 'signs in a valid non-2FA user and redirects' do
21
+ user = create(:admin, password: password)
22
+
23
+ post :create, params: { user: { email: user.email, password: password } }
24
+
25
+ expect(controller).to have_received(:sign_in).with(:user, user)
26
+ expect(response).to redirect_to(admin_pages_path)
27
+ end
28
+
29
+ it 'starts the 2FA handoff for an OTP-enabled user' do
30
+ user = create(:admin, password: password, otp_required_for_login: true)
31
+
32
+ post :create, params: { user: { email: user.email, password: password } }
33
+
34
+ expect(response).to redirect_to(admin_two_factor_path)
35
+ expect(session[:pre_2fa_user_id]).to eq(user.id)
36
+ expect(session[:pre_2fa_started_at]).to be_present
37
+ expect(controller).not_to have_received(:sign_in)
38
+ end
39
+
40
+ it 're-renders the sign-in form on bad credentials' do
41
+ user = create(:admin, password: password)
42
+
43
+ post :create, params: { user: { email: user.email, password: 'wrong' } }
44
+
45
+ expect(controller).not_to have_received(:sign_in)
46
+ expect(flash.now[:alert]).to be_present
47
+ end
48
+
49
+ it 're-renders the sign-in form for an unknown email' do
50
+ post :create, params: { user: { email: 'nobody@example.com', password: password } }
51
+
52
+ expect(controller).not_to have_received(:sign_in)
53
+ expect(flash.now[:alert]).to be_present
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+
3
+ # Exercises Admin::TwoFactorController — the mid-login TOTP challenge. It runs
4
+ # BEFORE the user is fully signed in (authenticate_user! is skipped) and drives
5
+ # the flow off a short-lived session handoff (:pre_2fa_user_id / :started_at).
6
+ RSpec.describe Admin::TwoFactorController, type: :controller do
7
+ routes { TrustyCms::Application.routes }
8
+ # current_user is read via Devise even though authenticate_user! is skipped.
9
+ include Devise::Test::ControllerHelpers
10
+
11
+ let(:user) { create(:admin, otp_required_for_login: true) }
12
+
13
+ # Put the user mid-2FA: pending id in the session, started just now.
14
+ def begin_2fa!(started_at: Time.current.to_i)
15
+ session[:pre_2fa_user_id] = user.id
16
+ session[:pre_2fa_started_at] = started_at
17
+ end
18
+
19
+ before { allow(controller).to receive(:sign_in) }
20
+
21
+ describe 'GET #show' do
22
+ it 'renders the challenge for a user mid-2FA' do
23
+ begin_2fa!
24
+ get :show
25
+ expect(response).to have_http_status(:ok)
26
+ end
27
+
28
+ it 'redirects to sign-in when there is no pending 2FA session' do
29
+ get :show
30
+ expect(response).to redirect_to(new_user_session_path)
31
+ expect(flash[:alert]).to be_present
32
+ end
33
+
34
+ it 'redirects to sign-in when the 2FA session has expired' do
35
+ begin_2fa!(started_at: 10.minutes.ago.to_i)
36
+ get :show
37
+ expect(response).to redirect_to(new_user_session_path)
38
+ end
39
+ end
40
+
41
+ describe 'POST #create' do
42
+ before { begin_2fa! }
43
+
44
+ it 'signs the user in and clears the handoff when the code is valid' do
45
+ allow_any_instance_of(User).to receive(:validate_and_consume_otp!).with('123456').and_return(true)
46
+
47
+ post :create, params: { otp_attempt: '123456' }
48
+
49
+ expect(controller).to have_received(:sign_in).with(:user, an_instance_of(User))
50
+ expect(response).to redirect_to(admin_pages_path)
51
+ expect(session[:pre_2fa_user_id]).to be_nil
52
+ end
53
+
54
+ it 'resets the session and redirects when the code is invalid' do
55
+ allow_any_instance_of(User).to receive(:validate_and_consume_otp!).and_return(false)
56
+
57
+ post :create, params: { otp_attempt: 'wrong' }
58
+
59
+ expect(response).to redirect_to(new_user_session_path)
60
+ expect(flash[:alert]).to be_present
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ # Exercises Admin::UsersController, which layers admin-only authorization and
4
+ # several custom overrides (create/update/destroy/disable_2fa, plus the
5
+ # self-protection guards) on top of ResourceController. Devise/2FA touch points
6
+ # make this a useful regression net for a Rails/Ruby upgrade.
7
+ RSpec.describe Admin::UsersController, type: :controller do
8
+ routes { TrustyCms::Application.routes }
9
+
10
+ let(:admin) { create(:admin) }
11
+
12
+ before do
13
+ allow(controller).to receive(:authenticate_user!).and_return(true)
14
+ allow(controller).to receive(:current_user).and_return(admin)
15
+ end
16
+
17
+ def valid_user_params(overrides = {})
18
+ {
19
+ first_name: 'New',
20
+ last_name: 'Person',
21
+ email: 'newperson@example.com',
22
+ password: 'ComplexPass1!',
23
+ password_confirmation: 'ComplexPass1!',
24
+ }.merge(overrides)
25
+ end
26
+
27
+ describe 'GET #index' do
28
+ it 'succeeds for an admin' do
29
+ get :index
30
+ expect(response).to have_http_status(:ok)
31
+ end
32
+ end
33
+
34
+ describe 'GET #new' do
35
+ it 'succeeds' do
36
+ get :new
37
+ expect(response).to have_http_status(:ok)
38
+ end
39
+ end
40
+
41
+ describe 'GET #show' do
42
+ it 'redirects to the edit page' do
43
+ user = create(:user, email: 'showme@example.com')
44
+ get :show, params: { id: user.id }
45
+ expect(response).to redirect_to(edit_admin_user_path(user.id))
46
+ end
47
+ end
48
+
49
+ describe 'POST #create' do
50
+ it 'creates a user and redirects with a notice' do
51
+ expect {
52
+ post :create, params: { user: valid_user_params }
53
+ }.to change(User, :count).by(1)
54
+
55
+ expect(response).to redirect_to(admin_users_path)
56
+ expect(flash[:notice]).to eq('User was created.')
57
+ end
58
+
59
+ it 're-renders new with an error when the user is invalid' do
60
+ expect {
61
+ post :create, params: { user: valid_user_params(email: '') }
62
+ }.not_to change(User, :count)
63
+
64
+ expect(flash[:error]).to match(/error saving the user/)
65
+ end
66
+ end
67
+
68
+ describe 'PUT #update' do
69
+ it 'updates an existing user and redirects' do
70
+ user = create(:user, email: 'editme@example.com')
71
+
72
+ put :update, params: { id: user.id, user: { first_name: 'Renamed' } }
73
+
74
+ expect(response).to be_redirect
75
+ expect(user.reload.first_name).to eq('Renamed')
76
+ end
77
+
78
+ # NOTE: latent bug — the guard compares `user_params['admin'] == false`, but
79
+ # over a real HTTP request the checkbox param arrives as the string 'false',
80
+ # which is != false. So through the normal request path an admin CAN demote
81
+ # themselves and no warning is shown. Characterizing current behavior;
82
+ # revisit if the guard is ever fixed to coerce the param.
83
+ it 'does NOT block self-demotion when admin arrives as the string "false" (bug)' do
84
+ put :update, params: { id: admin.id, user: { admin: 'false' } }
85
+
86
+ expect(admin.reload.admin).to be(false)
87
+ end
88
+ end
89
+
90
+ describe 'DELETE #destroy' do
91
+ it 'destroys another user' do
92
+ user = create(:user, email: 'goner@example.com')
93
+
94
+ expect {
95
+ delete :destroy, params: { id: user.id }
96
+ }.to change(User, :count).by(-1)
97
+ end
98
+
99
+ it 'refuses to let a user delete themselves' do
100
+ expect {
101
+ delete :destroy, params: { id: admin.id }
102
+ }.not_to change(User, :count)
103
+
104
+ expect(response).to redirect_to(admin_users_path)
105
+ expect(flash[:error]).to be_present
106
+ end
107
+ end
108
+
109
+ describe 'PATCH #disable_2fa' do
110
+ it 'clears the OTP settings and redirects' do
111
+ user = create(:user, email: '2fa@example.com', otp_required_for_login: true)
112
+
113
+ patch :disable_2fa, params: { id: user.id }
114
+
115
+ user.reload
116
+ expect(user.otp_required_for_login).to be(false)
117
+ expect(user.otp_secret).to be_nil
118
+ expect(response).to redirect_to(admin_users_path)
119
+ end
120
+ end
121
+
122
+ describe 'authorization' do
123
+ it 'denies a non-admin user' do
124
+ allow(controller).to receive(:current_user).and_return(create(:non_admin, email: 'plain@example.com'))
125
+
126
+ get :index
127
+
128
+ expect(response).to be_redirect
129
+ expect(flash[:error]).to be_present
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ # Exercises PageStatusController#refresh — a token-authenticated, no-CSRF JSON
4
+ # endpoint that publishes scheduled pages whose time has come. Auth is a bearer
5
+ # token compared with secure_compare; cover the reject paths and the publish
6
+ # behaviour.
7
+ RSpec.describe PageStatusController, type: :controller do
8
+ routes { TrustyCms::Application.routes }
9
+ # refresh skips authenticate_user!, but ApplicationController before-actions
10
+ # still read current_user, which needs Warden injected on the request.
11
+ include Devise::Test::ControllerHelpers
12
+
13
+ let(:token) { 'secret-bearer-token' }
14
+
15
+ def authorize!(value = token)
16
+ request.headers['Authorization'] = "Bearer #{value}"
17
+ end
18
+
19
+ before do
20
+ allow(Rails.application.credentials).to receive(:dig)
21
+ .with(:trusty_cms, :page_status_bearer_token).and_return(token)
22
+ end
23
+
24
+ describe 'POST #refresh authentication' do
25
+ it 'returns 401 when no token is provided' do
26
+ post :refresh
27
+ expect(response).to have_http_status(:unauthorized)
28
+ expect(JSON.parse(response.body)['error']).to eq('Missing Bearer Token')
29
+ end
30
+
31
+ it 'returns 401 when the token is wrong' do
32
+ authorize!('nope')
33
+ post :refresh
34
+ expect(response).to have_http_status(:unauthorized)
35
+ expect(JSON.parse(response.body)['error']).to eq('Invalid Bearer Token')
36
+ end
37
+ end
38
+
39
+ describe 'POST #refresh' do
40
+ before { authorize! }
41
+
42
+ it 'publishes scheduled pages whose publish time has passed' do
43
+ due = create(:page, title: 'Due', slug: 'due', status_id: Status[:scheduled].id,
44
+ published_at: 1.day.ago)
45
+
46
+ post :refresh
47
+
48
+ expect(response).to have_http_status(:ok)
49
+ body = JSON.parse(response.body)
50
+ expect(body['updated_page_ids']).to include(due.id)
51
+ expect(due.reload.status_id).to eq(Status[:published].id)
52
+ end
53
+
54
+ it 'leaves future-scheduled pages alone' do
55
+ future = create(:page, title: 'Future', slug: 'future', status_id: Status[:scheduled].id,
56
+ published_at: 1.day.from_now)
57
+
58
+ post :refresh
59
+
60
+ body = JSON.parse(response.body)
61
+ expect(body['remaining_scheduled_page_ids']).to include(future.id)
62
+ expect(body['message']).to match(/No scheduled pages/)
63
+ expect(future.reload.status_id).to eq(Status[:scheduled].id)
64
+ end
65
+ end
66
+ end
data/spec/dummy/bin/rails CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- APP_PATH = File.expand_path('../../config/application', __FILE__)
3
- require_relative '../config/boot'
4
- require 'rails/commands'
2
+ APP_PATH = File.expand_path("../config/application", __dir__)
3
+ require_relative "../config/boot"
4
+ require "rails/commands"
data/spec/dummy/bin/rake CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
- require_relative '../config/boot'
3
- require 'rake'
2
+ require_relative "../config/boot"
3
+ require "rake"
4
4
  Rake.application.run
data/spec/dummy/bin/setup CHANGED
@@ -1,29 +1,37 @@
1
1
  #!/usr/bin/env ruby
2
- require 'pathname'
2
+ require "fileutils"
3
3
 
4
- # path to your application root.
5
- APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
4
+ APP_ROOT = File.expand_path("..", __dir__)
5
+ APP_NAME = "trusty-cms"
6
6
 
7
- Dir.chdir APP_ROOT do
8
- # This script is a starting point to setup your application.
9
- # Add necessary setup steps to this file:
7
+ def system!(*args)
8
+ system(*args, exception: true)
9
+ end
10
+
11
+ FileUtils.chdir APP_ROOT do
12
+ # This script is a way to set up or update your development environment automatically.
13
+ # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14
+ # Add necessary setup steps to this file.
10
15
 
11
16
  puts "== Installing dependencies =="
12
- system "gem install bundler --conservative"
13
- system "bundle check || bundle install"
17
+ system! "gem install bundler --conservative"
18
+ system("bundle check") || system!("bundle install")
14
19
 
15
20
  # puts "\n== Copying sample files =="
16
21
  # unless File.exist?("config/database.yml")
17
- # system "cp config/database.yml.sample config/database.yml"
22
+ # FileUtils.cp "config/database.yml.sample", "config/database.yml"
18
23
  # end
19
24
 
20
25
  puts "\n== Preparing database =="
21
- system "bin/rake db:setup"
26
+ system! "bin/rails db:prepare"
22
27
 
23
28
  puts "\n== Removing old logs and tempfiles =="
24
- system "rm -f log/*"
25
- system "rm -rf tmp/cache"
29
+ system! "bin/rails log:clear tmp:clear"
26
30
 
27
31
  puts "\n== Restarting application server =="
28
- system "touch tmp/restart.txt"
32
+ system! "bin/rails restart"
33
+
34
+ # puts "\n== Configuring puma-dev =="
35
+ # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}"
36
+ # system "curl -Is https://#{APP_NAME}.test/up | head -n 1"
29
37
  end
@@ -2,4 +2,4 @@
2
2
  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
3
3
 
4
4
  require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
5
- #$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)
5
+ #$LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__)