Skip to content

Commit ddff030

Browse files
committed
improve specs. add caching for resource
1 parent a67a5f9 commit ddff030

File tree

2 files changed

+140
-22
lines changed

2 files changed

+140
-22
lines changed

app/controllers/users/passwords_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def empty_fields_error
9595
end
9696

9797
def invalid_phone_number_error(error_message)
98+
@resource ||= resource
9899
@resource.errors.add(:phone_number, error_message)
99100

100101
false

spec/requests/users/passwords_spec.rb

Lines changed: 139 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@
44
let!(:org) { create(:casa_org) }
55
let!(:user) { create(:user, phone_number: "+12222222222", casa_org: org) }
66

7-
let!(:twillio_service_double) { instance_double(TwilioService) }
7+
let!(:twilio_service_double) { instance_double(TwilioService) }
88
let!(:short_url_service_double) { instance_double(ShortUrlService) }
99

1010
before do
1111
allow(TwilioService).to(
1212
receive(:new).with(
1313
org
14-
).and_return(twillio_service_double)
14+
).and_return(twilio_service_double)
1515
)
1616

17-
allow(twillio_service_double).to receive(:send_sms)
17+
allow(twilio_service_double).to receive(:send_sms)
1818

1919
allow(ShortUrlService).to receive(:new).and_return(short_url_service_double)
2020

@@ -37,14 +37,14 @@
3737

3838
it "sends a password reset SMS to existing user" do
3939
request
40-
expect(twillio_service_double).to have_received(:send_sms).once.with(
40+
expect(twilio_service_double).to have_received(:send_sms).once.with(
4141
{From: org.twilio_phone_number, Body: a_string_matching("reset_url"), To: user.phone_number}
4242
)
4343
end
4444

4545
it "sends a password reset email to existing user" do
46-
expect_any_instance_of(User).to receive(:send_reset_password_instructions).once
47-
request
46+
expect { request }.to change { ActionMailer::Base.deliveries.count }.by(1)
47+
expect(ActionMailer::Base.deliveries.last.to).to include(user.email)
4848
end
4949

5050
it { is_expected.to redirect_to(user_session_url) }
@@ -60,13 +60,13 @@
6060
let(:params) { {user: {email: user.email, phone_number: ""}} }
6161

6262
it "sends a password reset email to existing user" do
63-
expect_any_instance_of(User).to receive(:send_reset_password_instructions).once
64-
request
63+
expect { request }.to change { ActionMailer::Base.deliveries.count }.by(1)
64+
expect(ActionMailer::Base.deliveries.last.to).to include(user.email)
6565
end
6666

6767
it "does not send sms with reset password" do
6868
request
69-
expect(twillio_service_double).not_to have_received(:send_sms)
69+
expect(twilio_service_double).not_to have_received(:send_sms)
7070
end
7171
end
7272

@@ -75,14 +75,13 @@
7575

7676
it "sends a password reset SMS to existing user" do
7777
request
78-
expect(twillio_service_double).to have_received(:send_sms).once.with(
78+
expect(twilio_service_double).to have_received(:send_sms).once.with(
7979
{From: org.twilio_phone_number, Body: a_string_matching("reset_url"), To: user.phone_number}
8080
)
8181
end
8282

8383
it "does not send email with reset password" do
84-
expect_any_instance_of(User).not_to receive(:send_reset_password_instructions)
85-
request
84+
expect { request }.not_to change { ActionMailer::Base.deliveries.count }
8685
end
8786
end
8887
end
@@ -96,17 +95,35 @@
9695
end
9796
end
9897

99-
context "with wrong parameters" do
98+
context "with wrong parameters (non-existent user)" do
10099
let(:params) { {user: {phone_number: "13333333333"}} }
101100

102-
it "sets errors correctly" do
101+
it "does not reveal if user exists (security)" do
103102
request
104103
expect(flash[:notice]).to(
105104
eq("If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.")
106105
)
107106
end
108107
end
109108

109+
context "with invalid phone number format" do
110+
let(:params) { {user: {email: "", phone_number: "1234"}} }
111+
112+
it "shows phone number validation error" do
113+
request
114+
expect(request.parsed_body.to_html).to include("phone_number")
115+
end
116+
end
117+
118+
context "with non-numeric phone number" do
119+
let(:params) { {user: {email: "", phone_number: "abc"}} }
120+
121+
it "shows phone number validation error" do
122+
request
123+
expect(request.parsed_body.to_html).to include("phone_number")
124+
end
125+
end
126+
110127
context "when twilio is disabled" do
111128
let(:params) { {user: {email: user.email, phone_number: user.phone_number}} }
112129

@@ -115,19 +132,56 @@
115132
end
116133

117134
it "does not send an sms, only an email" do
118-
expect_any_instance_of(User).to receive(:send_reset_password_instructions).once
135+
expect { request }.to change { ActionMailer::Base.deliveries.count }.by(1)
136+
expect(flash[:notice]).to(
137+
eq("If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.")
138+
)
139+
end
140+
end
141+
142+
context "when email sending times out with Net::ReadTimeout" do
143+
let(:params) { {user: {email: user.email, phone_number: user.phone_number}} }
144+
145+
before do
146+
allow(user).to receive(:send_reset_password_instructions).and_raise(Net::ReadTimeout)
147+
allow(User).to receive(:find_by).and_return(user)
148+
end
149+
150+
it "handles the timeout gracefully and still shows success message" do
151+
expect(Rails.logger).to receive(:error).with(/Password reset email failed to send/)
119152
request
120153
expect(flash[:notice]).to(
121154
eq("If the account exists you will receive an email or SMS with instructions on how to reset your password in a few minutes.")
122155
)
123156
end
157+
158+
it "does not crash the request" do
159+
expect { request }.not_to raise_error
160+
expect(response).to redirect_to(user_session_url)
161+
end
162+
163+
it "notifies Bugsnag of the error" do
164+
expect(Bugsnag).to receive(:notify).with(instance_of(Net::ReadTimeout))
165+
request
166+
end
167+
168+
it "generates a fallback token for SMS to use" do
169+
expect(user).to receive(:generate_password_reset_token).and_call_original
170+
request
171+
end
172+
173+
it "still sends SMS with the fallback token" do
174+
request
175+
expect(twilio_service_double).to have_received(:send_sms).once
176+
end
124177
end
125178

126-
context "when email sending times out" do
179+
context "when email sending times out with Net::OpenTimeout" do
127180
let(:params) { {user: {email: user.email, phone_number: ""}} }
128181

129182
before do
130-
allow_any_instance_of(User).to receive(:send_reset_password_instructions).and_raise(Net::ReadTimeout)
183+
allow(user).to receive(:send_reset_password_instructions).and_raise(Net::OpenTimeout)
184+
allow(User).to receive(:find_by).and_return(user)
131185
end
132186

133187
it "handles the timeout gracefully and still shows success message" do
@@ -142,6 +196,23 @@
142196
expect { request }.not_to raise_error
143197
expect(response).to redirect_to(user_session_url)
144198
end
199+
200+
it "notifies Bugsnag of the error" do
201+
expect(Bugsnag).to receive(:notify).with(instance_of(Net::OpenTimeout))
202+
request
203+
end
204+
end
205+
206+
context "when SMS sending fails" do
207+
let(:params) { {user: {email: "", phone_number: user.phone_number}} }
208+
209+
before do
210+
allow(twilio_service_double).to receive(:send_sms).and_raise(Twilio::REST::TwilioError.new("Service unavailable"))
211+
end
212+
213+
it "raises the error (no rescue in controller)" do
214+
expect { request }.to raise_error(Twilio::REST::TwilioError)
215+
end
145216
end
146217
end
147218

@@ -162,12 +233,58 @@
162233
}
163234
end
164235

165-
subject(:submit_reset) { put user_password_path, params: params }
236+
subject(:request) { put user_password_path, params: params }
166237

167-
it "successfully resets the password" do
168-
submit_reset
169-
expect(response).to redirect_to(new_user_session_path)
170-
expect(flash[:notice]).to eq("Your password has been changed successfully.")
238+
context "with valid token and password" do
239+
it "successfully resets the password" do
240+
request
241+
expect(response).to redirect_to(new_user_session_path)
242+
expect(flash[:notice]).to eq("Your password has been changed successfully.")
243+
end
244+
245+
it "allows user to sign in with new password" do
246+
request
247+
user.reload
248+
expect(user.valid_password?("newpassword123!")).to be true
249+
end
250+
end
251+
252+
context "with password mismatch" do
253+
let(:params) do
254+
{
255+
user: {
256+
reset_password_token: token,
257+
password: "newpassword123!",
258+
password_confirmation: "differentpassword123!"
259+
}
260+
}
261+
end
262+
263+
it "does not reset the password" do
264+
old_password_digest = user.encrypted_password
265+
request
266+
user.reload
267+
expect(user.encrypted_password).to eq(old_password_digest)
268+
end
269+
end
270+
271+
context "with password too short" do
272+
let(:params) do
273+
{
274+
user: {
275+
reset_password_token: token,
276+
password: "abc",
277+
password_confirmation: "abc"
278+
}
279+
}
280+
end
281+
282+
it "does not reset the password" do
283+
old_password_digest = user.encrypted_password
284+
request
285+
user.reload
286+
expect(user.encrypted_password).to eq(old_password_digest)
287+
end
171288
end
172289
end
173290
end

0 commit comments

Comments
 (0)