Skip to content

Commit b39afff

Browse files
committed
FIX: Show error highlighting on password field in finish installation form
When submitting the admin registration form with an invalid password (blank or too short), the password field was not visually highlighted with the error state, making it unclear what was wrong. Two issues were causing this: 1. Copy-paste error: The password field container was checking `@user.errors[:username]` instead of `@user.errors[:password]` 2. Wrong error key: Password length validation errors from the UserPassword model are stored under `:"user_password.password"`, not `:password`. The template only checked the latter. Fixed by checking both error keys and displaying all password-related errors with proper visual highlighting. Added system spec with page object for UI behavior testing and trimmed request spec to cover edge cases that require stubs (like empty developer_emails). Ref - https://meta.discourse.org/t/381505
1 parent 52ab909 commit b39afff

File tree

4 files changed

+198
-120
lines changed

4 files changed

+198
-120
lines changed

app/views/finish_installation/register.html.erb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
<%- end %>
3131
</div>
3232

33-
<div class='wizard-container__input wizard-container__field text-field <% if @user.errors[:username].present? %>invalid<% end %>'>
33+
<%- password_errors = @user.errors[:password] + @user.errors[:"user_password.password"] %>
34+
<div class='wizard-container__input wizard-container__field text-field <% if password_errors.present? %>invalid<% end %>'>
3435
<label for="password">
3536
<span class="wizard-container__label"><%= t 'js.user.password.title' %></span>
3637
</label>
@@ -40,7 +41,7 @@
4041
<div class='input-area'>
4142
<%= password_field_tag(:password, params[:password], class:"wizard-container__text-input") %>
4243
</div>
43-
<% @user.errors[:password].each do |e| %>
44+
<% password_errors.each do |e| %>
4445
<div class='field-error-description'><%= e.to_s %></div>
4546
<% end %>
4647
</div>
Lines changed: 32 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,65 @@
11
# frozen_string_literal: true
22

33
RSpec.describe FinishInstallationController do
4-
describe "#index" do
5-
context "when has_login_hint is false" do
6-
before { SiteSetting.has_login_hint = false }
7-
8-
it "doesn't allow access" do
9-
get "/finish-installation"
10-
expect(response.status).to eq(403)
11-
end
12-
end
13-
14-
context "when has_login_hint is true" do
15-
before { SiteSetting.has_login_hint = true }
16-
17-
it "allows access" do
18-
get "/finish-installation"
19-
expect(response.status).to eq(200)
20-
end
21-
end
22-
end
23-
244
describe "#register" do
25-
context "when has_login_hint is false" do
26-
before { SiteSetting.has_login_hint = false }
27-
28-
it "doesn't allow access" do
29-
get "/finish-installation/register"
30-
expect(response.status).to eq(403)
31-
end
5+
before do
6+
SiteSetting.has_login_hint = true
7+
GlobalSetting.stubs(:developer_emails).returns("[email protected]")
328
end
339

34-
context "when has_login_hint is true" do
35-
before do
36-
SiteSetting.has_login_hint = true
37-
GlobalSetting.stubs(:developer_emails).returns("[email protected]")
38-
end
39-
40-
it "allows access" do
41-
get "/finish-installation/register"
42-
expect(response.status).to eq(200)
43-
end
44-
45-
it "raises an error when the email is not in the allowed list" do
46-
post "/finish-installation/register.json",
47-
params: {
48-
49-
username: "eviltrout",
50-
password: "disismypasswordokay",
51-
}
52-
expect(response.status).to eq(400)
53-
end
54-
55-
it "doesn't redirect when fields are wrong" do
56-
post "/finish-installation/register",
57-
params: {
58-
59-
username: "",
60-
password: "disismypasswordokay",
61-
}
62-
63-
expect(response).not_to be_redirect
64-
end
65-
66-
context "with working params" do
67-
let(:params) do
68-
{ email: "[email protected]", username: "eviltrout", password: "disismypasswordokay" }
69-
end
70-
71-
it "registers the admin when the email is in the list" do
72-
expect do post "/finish-installation/register.json", params: params end.to change {
73-
Jobs::CriticalUserEmail.jobs.size
74-
}.by(1)
75-
76-
expect(response).to be_redirect
77-
expect(User.where(username: "eviltrout").exists?).to eq(true)
78-
end
79-
80-
it "automatically resends the signup email when the user already exists" do
81-
expect do post "/finish-installation/register.json", params: params end.to change {
82-
Jobs::CriticalUserEmail.jobs.size
83-
}.by(1)
84-
85-
expect(User.where(username: "eviltrout").exists?).to eq(true)
86-
87-
expect do post "/finish-installation/register.json", params: params end.to change {
88-
Jobs::CriticalUserEmail.jobs.size
89-
}.by(1)
90-
91-
expect(response).to be_redirect
92-
expect(User.where(username: "eviltrout").exists?).to eq(true)
93-
end
94-
end
95-
96-
it "sets the admins trust level" do
97-
post "/finish-installation/register.json",
98-
params: {
99-
100-
username: "eviltrout",
101-
password: "disismypasswordokay",
102-
}
10+
it "shows no_emails message when developer_emails is empty" do
11+
GlobalSetting.stubs(:developer_emails).returns("")
12+
get "/finish-installation/register"
13+
expect(response.status).to eq(200)
14+
expect(response.body).to include(I18n.t("finish_installation.register.no_emails"))
15+
end
10316

104-
expect(User.find_by(username: "eviltrout").trust_level).to eq 1
105-
end
17+
it "returns 400 when email is not in the allowed list" do
18+
post "/finish-installation/register.json",
19+
params: {
20+
21+
username: "eviltrout",
22+
password: "disismypasswordokay",
23+
}
24+
expect(response.status).to eq(400)
10625
end
10726
end
10827

10928
describe "#confirm_email" do
110-
context "when has_login_hint is false" do
111-
before { SiteSetting.has_login_hint = false }
112-
113-
it "shows the page" do
114-
get "/finish-installation/confirm-email"
115-
expect(response.status).to eq(200)
116-
end
29+
it "renders without requiring has_login_hint" do
30+
SiteSetting.has_login_hint = false
31+
get "/finish-installation/confirm-email"
32+
expect(response.status).to eq(200)
11733
end
11834
end
11935

12036
describe "#resend_email" do
12137
before do
12238
SiteSetting.has_login_hint = true
12339
GlobalSetting.stubs(:developer_emails).returns("[email protected]")
40+
end
12441

42+
it "resends activation email for user in session" do
12543
post "/finish-installation/register",
12644
params: {
12745
12846
username: "eviltrout",
12947
password: "disismypasswordokay",
13048
}
131-
end
13249

133-
it "resends the email" do
134-
expect do put "/finish-installation/resend-email" end.to change {
50+
expect { put "/finish-installation/resend-email" }.to change {
13551
Jobs::CriticalUserEmail.jobs.size
13652
}.by(1)
13753

13854
expect(response.status).to eq(200)
13955
end
56+
57+
it "does nothing when user doesn't exist" do
58+
expect { put "/finish-installation/resend-email" }.not_to change {
59+
Jobs::CriticalUserEmail.jobs.size
60+
}
61+
62+
expect(response.status).to eq(200)
63+
end
14064
end
14165
end
Lines changed: 88 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,98 @@
11
# frozen_string_literal: true
22

33
RSpec.describe "Finish Installation", type: :system do
4-
before do
5-
SiteSetting.has_login_hint = true
6-
GlobalSetting.stubs(:developer_emails).returns("[email protected]")
4+
let(:finish_installation_page) { PageObjects::Pages::FinishInstallation.new }
5+
6+
context "when has_login_hint is false" do
7+
before { SiteSetting.has_login_hint = false }
8+
9+
it "denies access" do
10+
finish_installation_page.visit_index
11+
expect(finish_installation_page).to have_access_denied
12+
end
713
end
814

9-
it "renders first screen" do
10-
visit "/finish-installation"
15+
context "when has_login_hint is true" do
16+
before do
17+
SiteSetting.has_login_hint = true
18+
GlobalSetting.stubs(:developer_emails).returns("[email protected],[email protected]")
19+
end
20+
21+
it "shows validation error when username is blank" do
22+
finish_installation_page.visit_register.fill_password("supersecurepassword").submit
23+
expect(finish_installation_page).to have_username_error
24+
end
25+
26+
it "shows validation error when password is blank" do
27+
finish_installation_page.visit_register.fill_username("newadmin").submit
28+
expect(finish_installation_page).to have_password_error
29+
end
30+
31+
it "shows validation error when password is too short" do
32+
finish_installation_page
33+
.visit_register
34+
.fill_username("newadmin")
35+
.fill_password("short")
36+
.submit
37+
expect(finish_installation_page).to have_password_error("too short")
38+
end
39+
40+
it "registers admin and redirects to confirm email page" do
41+
finish_installation_page
42+
.visit_register
43+
.select_email("[email protected]")
44+
.fill_username("newadmin")
45+
.fill_password("supersecurepassword")
46+
.submit
47+
48+
expect(finish_installation_page).to be_redirected_to_confirm_email
49+
expect(User.find_by(username: "newadmin")).to have_attributes(
50+
51+
trust_level: 1,
52+
)
53+
end
54+
55+
it "handles multiple developer emails" do
56+
finish_installation_page
57+
.visit_register
58+
.select_email("[email protected]")
59+
.fill_username("otheradmin")
60+
.fill_password("supersecurepassword")
61+
.submit
62+
63+
expect(finish_installation_page).to be_redirected_to_confirm_email
64+
expect(User.find_by(username: "otheradmin")).to be_present
65+
end
66+
67+
it "resends activation email when user already exists" do
68+
Fabricate(:user, email: "[email protected]")
69+
70+
expect {
71+
finish_installation_page
72+
.visit_register
73+
.select_email("[email protected]")
74+
.fill_username("differentuser")
75+
.fill_password("supersecurepassword")
76+
.submit
77+
}.to change { Jobs::CriticalUserEmail.jobs.size }.by(1)
78+
79+
expect(finish_installation_page).to be_redirected_to_confirm_email
80+
end
1181

12-
find(".finish-installation__register").click
82+
it "does not send email when user is already active and confirmed" do
83+
user = Fabricate(:user, email: "[email protected]", active: true)
84+
user.activate
1385

14-
expect(page).to have_css(".wizard-container__combobox") # email field
15-
expect(page).to have_css(".input-area")
16-
expect(page).to have_css(".wizard-container__button") # submit button
86+
expect {
87+
finish_installation_page
88+
.visit_register
89+
.select_email("[email protected]")
90+
.fill_username("differentuser")
91+
.fill_password("supersecurepassword")
92+
.submit
93+
}.not_to change { Jobs::CriticalUserEmail.jobs.size }
1794

18-
# TODO: add more steps here to ensure full flow works
95+
expect(finish_installation_page).to be_redirected_to_confirm_email
96+
end
1997
end
2098
end
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# frozen_string_literal: true
2+
3+
module PageObjects
4+
module Pages
5+
class FinishInstallation < PageObjects::Pages::Base
6+
def visit_register
7+
visit("/finish-installation/register")
8+
self
9+
end
10+
11+
def visit_index
12+
visit("/finish-installation")
13+
self
14+
end
15+
16+
def has_register_form?
17+
has_css?("form.wizard-container__fields")
18+
end
19+
20+
def has_no_register_form?
21+
has_no_css?("form.wizard-container__fields")
22+
end
23+
24+
def has_no_emails_message?
25+
has_css?("p", text: I18n.t("finish_installation.register.no_emails"))
26+
end
27+
28+
def has_access_denied?
29+
has_css?(".not-found-container") || page.status_code == 403
30+
end
31+
32+
def fill_username(username)
33+
find("#username").fill_in(with: username)
34+
self
35+
end
36+
37+
def fill_password(password)
38+
find("#password").fill_in(with: password)
39+
self
40+
end
41+
42+
def select_email(email)
43+
find("#email").select(email)
44+
self
45+
end
46+
47+
def submit
48+
find("input[type='submit']").click
49+
self
50+
end
51+
52+
def has_username_error?(message = nil)
53+
field = find(".wizard-container__field", text: "Username")
54+
return false if field[:class].exclude?("invalid")
55+
return true if message.nil?
56+
field.has_css?(".field-error-description", text: message)
57+
end
58+
59+
def has_password_error?(message = nil)
60+
field = find(".wizard-container__field", text: "Password")
61+
return false if field[:class].exclude?("invalid")
62+
return true if message.nil?
63+
field.has_css?(".field-error-description", text: message)
64+
end
65+
66+
def has_no_field_errors?
67+
has_no_css?(".wizard-container__field.invalid")
68+
end
69+
70+
def redirected_to_confirm_email?
71+
has_current_path?("/finish-installation/confirm-email")
72+
end
73+
end
74+
end
75+
end

0 commit comments

Comments
 (0)