Skip to content

Commit 1b11ced

Browse files
committed
feat(platform): add protected attribute to CSS blocks to prevent deletion
feat(block_attributes): include Protected module for better content management fix(factory): update user factory to use confirmed trait for test consistency test(platforms_controller): enhance CSS block update tests for better coverage
1 parent cb5bb0c commit 1b11ced

File tree

6 files changed

+239
-8
lines changed

6 files changed

+239
-8
lines changed

app/models/better_together/platform.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,21 @@ def css_block?
6969
end
7070

7171
def css_block_attributes=(attrs = {})
72+
# Clear memoized css_block to ensure we get the latest state
73+
@css_block = nil
74+
75+
new_attrs = attrs.except(:type).merge(protected: true, privacy: 'public')
76+
7277
block = blocks.find_by(type: 'BetterTogether::Content::Css')
7378
if block
74-
block.update(attrs.except(:type))
79+
# Update the existing block directly and save it
80+
block.update!(new_attrs)
81+
@css_block = block
7582
else
76-
platform_blocks.build(block: BetterTogether::Content::Css.new(attrs.except(:type)))
83+
# Platform CSS blocks should be protected from deletion
84+
new_block = BetterTogether::Content::Css.new(new_attrs)
85+
platform_blocks.build(block: new_block)
86+
@css_block = new_block
7787
end
7888
end
7989

app/models/concerns/better_together/content/block_attributes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ module BlockAttributes # rubocop:todo Metrics/ModuleLength, Style/Documentation
2222

2323
include ::BetterTogether::Creatable
2424
include ::BetterTogether::Privacy
25+
include ::BetterTogether::Protected
2526
include ::BetterTogether::Translatable
2627
include ::BetterTogether::Visible
2728

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# frozen_string_literal: true
2+
3+
# Adds protected column to prevent deletion of platform-critical blocks
4+
class AddProtectedToBetterTogetherContentBlocks < ActiveRecord::Migration[8.0]
5+
def change
6+
change_table :better_together_content_blocks, bulk: true, &:bt_protected
7+
end
8+
end

spec/factories/better_together/users.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
aliases: %i[user]) do
99
id { Faker::Internet.uuid }
1010
email { Faker::Internet.unique.email }
11-
password { Faker::Internet.password(min_length: 12, max_length: 20) }
11+
password { 'SecureTest123!@#' }
1212

1313
person
1414

spec/requests/better_together/platforms_controller_spec.rb

Lines changed: 215 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,230 @@
1919
end
2020

2121
describe 'PATCH /:locale/.../host/platforms/:id' do
22+
let(:host_platform) { BetterTogether::Platform.find_by(host: true) }
23+
2224
# rubocop:todo RSpec/MultipleExpectations
2325
it 'updates settings and redirects' do # rubocop:todo RSpec/MultipleExpectations
2426
# rubocop:enable RSpec/MultipleExpectations
25-
host_platform = BetterTogether::Platform.find_by(host: true)
2627
patch better_together.platform_path(locale:, id: host_platform.slug), params: {
2728
platform: { url: host_platform.url, time_zone: host_platform.time_zone, requires_invitation: true }
2829
}
2930
expect(response).to have_http_status(:see_other)
3031
follow_redirect!
3132
expect(response).to have_http_status(:ok)
3233
end
34+
35+
context 'when updating CSS block' do
36+
context 'when platform has no existing CSS block' do # rubocop:todo RSpec/NestedGroups
37+
before do
38+
# Ensure platform starts without a CSS block
39+
host_platform.blocks.where(type: 'BetterTogether::Content::Css').destroy_all
40+
host_platform.reload
41+
end
42+
43+
it 'creates a new CSS block with content' do # rubocop:todo RSpec/MultipleExpectations
44+
css_content = '.my-custom-class { color: blue; }'
45+
46+
expect do
47+
patch better_together.platform_path(locale:, id: host_platform.slug), params: {
48+
platform: {
49+
url: host_platform.url,
50+
time_zone: host_platform.time_zone,
51+
css_block_attributes: {
52+
type: 'BetterTogether::Content::Css',
53+
identifier: 'platform-custom-css',
54+
"content_#{locale}": css_content
55+
}
56+
}
57+
}
58+
end.to change { host_platform.reload.css_block.present? }.from(false).to(true)
59+
60+
expect(response).to have_http_status(:see_other)
61+
62+
css_block = host_platform.reload.css_block
63+
expect(css_block).to be_present
64+
expect(css_block.content).to eq(css_content)
65+
expect(css_block.identifier).to eq('platform-custom-css')
66+
expect(css_block.protected).to be(true)
67+
end
68+
69+
it 'creates CSS block with complex real-world CSS' do # rubocop:todo RSpec/MultipleExpectations
70+
complex_css = <<~CSS
71+
.notification form[action*="mark_as_read"] .btn[type="submit"] { z-index: 1200; }
72+
.card.journey-stage > .card-body { max-height: 50vh; }
73+
@media only screen and (min-width: 768px) {
74+
.hero-heading { font-size: 3em; }
75+
}
76+
CSS
77+
78+
patch better_together.platform_path(locale:, id: host_platform.slug), params: {
79+
platform: {
80+
url: host_platform.url,
81+
time_zone: host_platform.time_zone,
82+
css_block_attributes: {
83+
type: 'BetterTogether::Content::Css',
84+
identifier: 'platform-custom-css',
85+
"content_#{locale}": complex_css
86+
}
87+
}
88+
}
89+
90+
expect(response).to have_http_status(:see_other)
91+
92+
css_block = host_platform.reload.css_block
93+
expect(css_block.content).to eq(complex_css)
94+
end
95+
end
96+
97+
context 'when platform has existing CSS block' do # rubocop:todo RSpec/NestedGroups
98+
let(:existing_css_block) do
99+
create(:better_together_content_css,
100+
identifier: 'existing-platform-css',
101+
content_text: '.old-class { color: red; }',
102+
protected: true)
103+
end
104+
105+
before do
106+
# Associate existing CSS block with platform
107+
host_platform.platform_blocks.create!(block: existing_css_block)
108+
host_platform.reload
109+
end
110+
111+
it 'updates existing CSS block content' do # rubocop:todo RSpec/MultipleExpectations
112+
new_css_content = '.updated-class { color: green; }'
113+
114+
patch better_together.platform_path(locale:, id: host_platform.slug), params: {
115+
platform: {
116+
url: host_platform.url,
117+
time_zone: host_platform.time_zone,
118+
css_block_attributes: {
119+
id: existing_css_block.id,
120+
type: 'BetterTogether::Content::Css',
121+
identifier: existing_css_block.identifier,
122+
"content_#{locale}": new_css_content
123+
}
124+
}
125+
}
126+
127+
expect(response).to have_http_status(:see_other)
128+
129+
existing_css_block.reload
130+
expect(existing_css_block.content).to eq(new_css_content)
131+
end
132+
133+
it 'preserves CSS block ID when updating' do # rubocop:todo RSpec/MultipleExpectations
134+
original_id = existing_css_block.id
135+
new_css_content = '.another-class { font-size: 14px; }'
136+
137+
patch better_together.platform_path(locale:, id: host_platform.slug), params: {
138+
platform: {
139+
url: host_platform.url,
140+
time_zone: host_platform.time_zone,
141+
css_block_attributes: {
142+
id: existing_css_block.id,
143+
type: 'BetterTogether::Content::Css',
144+
identifier: existing_css_block.identifier,
145+
"content_#{locale}": new_css_content
146+
}
147+
}
148+
}
149+
150+
expect(response).to have_http_status(:see_other)
151+
152+
css_block = host_platform.reload.css_block
153+
expect(css_block.id).to eq(original_id)
154+
expect(css_block.content).to eq(new_css_content)
155+
end
156+
157+
it 'handles CSS with special characters and quotes' do # rubocop:todo RSpec/MultipleExpectations
158+
css_with_quotes = <<~CSS
159+
.notification form[action*="mark_as_read"] .btn[type="submit"] {
160+
z-index: 1200;
161+
position: relative;
162+
}
163+
.trix-content a[href]:not([href*="example.com"])::after {
164+
content: "\\f35d";
165+
font-family: "Font Awesome 6 Free";
166+
}
167+
CSS
168+
169+
patch better_together.platform_path(locale:, id: host_platform.slug), params: {
170+
platform: {
171+
url: host_platform.url,
172+
time_zone: host_platform.time_zone,
173+
css_block_attributes: {
174+
id: existing_css_block.id,
175+
type: 'BetterTogether::Content::Css',
176+
identifier: existing_css_block.identifier,
177+
"content_#{locale}": css_with_quotes
178+
}
179+
}
180+
}
181+
182+
expect(response).to have_http_status(:see_other)
183+
184+
existing_css_block.reload
185+
expect(existing_css_block.content).to eq(css_with_quotes)
186+
# Verify special characters are preserved
187+
expect(existing_css_block.content).to include('[action*="mark_as_read"]')
188+
expect(existing_css_block.content).to include('content: "\\f35d"')
189+
end
190+
191+
it 'clears CSS content when empty string submitted' do # rubocop:todo RSpec/MultipleExpectations
192+
patch better_together.platform_path(locale:, id: host_platform.slug), params: {
193+
platform: {
194+
url: host_platform.url,
195+
time_zone: host_platform.time_zone,
196+
css_block_attributes: {
197+
id: existing_css_block.id,
198+
type: 'BetterTogether::Content::Css',
199+
identifier: existing_css_block.identifier,
200+
"content_#{locale}": ''
201+
}
202+
}
203+
}
204+
205+
expect(response).to have_http_status(:see_other)
206+
207+
existing_css_block.reload
208+
expect(existing_css_block.content).to be_blank
209+
end
210+
end
211+
212+
context 'when CSS block update fails validation' do # rubocop:todo RSpec/NestedGroups
213+
let(:existing_css_block) do
214+
create(:better_together_content_css,
215+
identifier: 'existing-platform-css',
216+
content_text: '.old-class { color: red; }',
217+
protected: true)
218+
end
219+
220+
before do
221+
host_platform.platform_blocks.create!(block: existing_css_block)
222+
host_platform.reload
223+
224+
# Stub validation to force failure
225+
allow_any_instance_of(BetterTogether::Platform).to receive(:update).and_return(false) # rubocop:todo RSpec/AnyInstance
226+
end
227+
228+
it 'renders edit form with unprocessable_content status' do # rubocop:todo RSpec/MultipleExpectations
229+
patch better_together.platform_path(locale:, id: host_platform.slug), params: {
230+
platform: {
231+
url: host_platform.url,
232+
time_zone: host_platform.time_zone,
233+
css_block_attributes: {
234+
id: existing_css_block.id,
235+
type: 'BetterTogether::Content::Css',
236+
identifier: existing_css_block.identifier,
237+
"content_#{locale}": '.new-class { color: blue; }'
238+
}
239+
}
240+
}
241+
242+
expect(response).to have_http_status(:unprocessable_content)
243+
expect(response).to render_template(:edit)
244+
end
245+
end
246+
end
33247
end
34248
end

spec/support/test_seed_helpers.rb

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@
77
user_email = '[email protected]'
88

99
unless BetterTogether::User.find_by(email: manager_email)
10-
person = BetterTogether::Person.create!(name: 'Manager Person')
11-
BetterTogether::User.create!(email: manager_email, password: 'SecureTest123!@#', person: person)
10+
FactoryBot.create(:better_together_user, :confirmed, email: manager_email)
1211
end
1312

1413
unless BetterTogether::User.find_by(email: user_email)
15-
person = BetterTogether::Person.create!(name: 'Test User')
16-
BetterTogether::User.create!(email: user_email, password: 'SecureTest123!@#', person: person)
14+
FactoryBot.create(:better_together_user, :confirmed, email: user_email)
1715
end
1816
end
1917
end

0 commit comments

Comments
 (0)