1+ using AutoFixture ;
2+ using FSH . Framework . Core . Context ;
3+ using FSH . Modules . Identity . Contracts . Services ;
4+ using FSH . Modules . Identity . Contracts . v1 . Users . ChangePassword ;
5+ using FSH . Modules . Identity . Features . v1 . Users . ChangePassword ;
6+ using NSubstitute ;
7+ using NSubstitute . ExceptionExtensions ;
8+
9+ namespace Identity . Tests . Handlers ;
10+
11+ /// <summary>
12+ /// Tests for ChangePasswordCommandHandler - handles password change operations.
13+ /// </summary>
14+ public sealed class ChangePasswordCommandHandlerTests
15+ {
16+ private readonly IUserService _userService ;
17+ private readonly ICurrentUser _currentUser ;
18+ private readonly ChangePasswordCommandHandler _sut ;
19+ private readonly IFixture _fixture ;
20+
21+ public ChangePasswordCommandHandlerTests ( )
22+ {
23+ _userService = Substitute . For < IUserService > ( ) ;
24+ _currentUser = Substitute . For < ICurrentUser > ( ) ;
25+ _sut = new ChangePasswordCommandHandler ( _userService , _currentUser ) ;
26+ _fixture = new Fixture ( ) ;
27+ }
28+
29+ #region Handle - Happy Path Tests
30+
31+ [ Fact ]
32+ public async Task Handle_Should_ReturnSuccessMessage_When_PasswordIsChangedSuccessfully ( )
33+ {
34+ // Arrange
35+ var command = new ChangePasswordCommand
36+ {
37+ Password = "CurrentPassword123!" ,
38+ NewPassword = "NewPassword456!" ,
39+ ConfirmNewPassword = "NewPassword456!"
40+ } ;
41+
42+ var userId = _fixture . Create < Guid > ( ) ;
43+
44+ _currentUser . IsAuthenticated ( ) . Returns ( true ) ;
45+ _currentUser . GetUserId ( ) . Returns ( userId ) ;
46+
47+ _userService . ChangePasswordAsync ( command . Password , command . NewPassword , command . ConfirmNewPassword , userId . ToString ( ) )
48+ . Returns ( Task . CompletedTask ) ;
49+
50+ // Act
51+ var result = await _sut . Handle ( command , CancellationToken . None ) ;
52+
53+ // Assert
54+ result . ShouldBe ( "password reset email sent" ) ;
55+ }
56+
57+ [ Fact ]
58+ public async Task Handle_Should_CallUserServiceWithCorrectParameters_When_PasswordChangeIsRequested ( )
59+ {
60+ // Arrange
61+ var command = new ChangePasswordCommand
62+ {
63+ Password = "OldPassword123!" ,
64+ NewPassword = "NewPassword456!" ,
65+ ConfirmNewPassword = "NewPassword456!"
66+ } ;
67+
68+ var userId = _fixture . Create < Guid > ( ) ;
69+
70+ _currentUser . IsAuthenticated ( ) . Returns ( true ) ;
71+ _currentUser . GetUserId ( ) . Returns ( userId ) ;
72+
73+ _userService . ChangePasswordAsync ( Arg . Any < string > ( ) , Arg . Any < string > ( ) , Arg . Any < string > ( ) , Arg . Any < string > ( ) )
74+ . Returns ( Task . CompletedTask ) ;
75+
76+ // Act
77+ await _sut . Handle ( command , CancellationToken . None ) ;
78+
79+ // Assert
80+ await _userService . Received ( 1 ) . ChangePasswordAsync (
81+ command . Password ,
82+ command . NewPassword ,
83+ command . ConfirmNewPassword ,
84+ userId . ToString ( ) ) ;
85+ }
86+
87+ #endregion
88+
89+ #region Handle - Authentication Tests
90+
91+ [ Fact ]
92+ public async Task Handle_Should_ThrowInvalidOperationException_When_UserIsNotAuthenticated ( )
93+ {
94+ // Arrange
95+ var command = new ChangePasswordCommand
96+ {
97+ Password = "CurrentPassword123!" ,
98+ NewPassword = "NewPassword456!" ,
99+ ConfirmNewPassword = "NewPassword456!"
100+ } ;
101+
102+ _currentUser . IsAuthenticated ( ) . Returns ( false ) ;
103+
104+ // Act & Assert
105+ var exception = await Should . ThrowAsync < InvalidOperationException > (
106+ async ( ) => await _sut . Handle ( command , CancellationToken . None ) ) ;
107+
108+ exception . Message . ShouldBe ( "User is not authenticated." ) ;
109+ }
110+
111+ [ Fact ]
112+ public async Task Handle_Should_CheckAuthentication_BeforeGettingUserId ( )
113+ {
114+ // Arrange
115+ var command = new ChangePasswordCommand
116+ {
117+ Password = "CurrentPassword123!" ,
118+ NewPassword = "NewPassword456!" ,
119+ ConfirmNewPassword = "NewPassword456!"
120+ } ;
121+
122+ _currentUser . IsAuthenticated ( ) . Returns ( false ) ;
123+
124+ // Act
125+ await Should . ThrowAsync < InvalidOperationException > (
126+ async ( ) => await _sut . Handle ( command , CancellationToken . None ) ) ;
127+
128+ // Assert
129+ _currentUser . Received ( 1 ) . IsAuthenticated ( ) ;
130+ _currentUser . DidNotReceive ( ) . GetUserId ( ) ;
131+ }
132+
133+ #endregion
134+
135+ #region Handle - Current User Tests
136+
137+ [ Fact ]
138+ public async Task Handle_Should_GetUserIdFromCurrentUser_When_UserIsAuthenticated ( )
139+ {
140+ // Arrange
141+ var command = new ChangePasswordCommand
142+ {
143+ Password = "CurrentPassword123!" ,
144+ NewPassword = "NewPassword456!" ,
145+ ConfirmNewPassword = "NewPassword456!"
146+ } ;
147+
148+ var userId = _fixture . Create < Guid > ( ) ;
149+
150+ _currentUser . IsAuthenticated ( ) . Returns ( true ) ;
151+ _currentUser . GetUserId ( ) . Returns ( userId ) ;
152+
153+ _userService . ChangePasswordAsync ( Arg . Any < string > ( ) , Arg . Any < string > ( ) , Arg . Any < string > ( ) , Arg . Any < string > ( ) )
154+ . Returns ( Task . CompletedTask ) ;
155+
156+ // Act
157+ await _sut . Handle ( command , CancellationToken . None ) ;
158+
159+ // Assert
160+ _currentUser . Received ( 1 ) . IsAuthenticated ( ) ;
161+ _currentUser . Received ( 1 ) . GetUserId ( ) ;
162+ }
163+
164+ [ Fact ]
165+ public async Task Handle_Should_ConvertUserIdToString_When_PassingToUserService ( )
166+ {
167+ // Arrange
168+ var command = new ChangePasswordCommand
169+ {
170+ Password = "CurrentPassword123!" ,
171+ NewPassword = "NewPassword456!" ,
172+ ConfirmNewPassword = "NewPassword456!"
173+ } ;
174+
175+ var userId = _fixture . Create < Guid > ( ) ;
176+
177+ _currentUser . IsAuthenticated ( ) . Returns ( true ) ;
178+ _currentUser . GetUserId ( ) . Returns ( userId ) ;
179+
180+ _userService . ChangePasswordAsync ( Arg . Any < string > ( ) , Arg . Any < string > ( ) , Arg . Any < string > ( ) , Arg . Any < string > ( ) )
181+ . Returns ( Task . CompletedTask ) ;
182+
183+ // Act
184+ await _sut . Handle ( command , CancellationToken . None ) ;
185+
186+ // Assert
187+ await _userService . Received ( 1 ) . ChangePasswordAsync (
188+ command . Password ,
189+ command . NewPassword ,
190+ command . ConfirmNewPassword ,
191+ userId . ToString ( ) ) ;
192+ }
193+
194+ #endregion
195+
196+ #region Handle - Exception Tests
197+
198+ [ Fact ]
199+ public async Task Handle_Should_ThrowException_When_UserServiceThrows ( )
200+ {
201+ // Arrange
202+ var command = new ChangePasswordCommand
203+ {
204+ Password = "WrongPassword123!" ,
205+ NewPassword = "NewPassword456!" ,
206+ ConfirmNewPassword = "NewPassword456!"
207+ } ;
208+
209+ var userId = _fixture . Create < Guid > ( ) ;
210+
211+ _currentUser . IsAuthenticated ( ) . Returns ( true ) ;
212+ _currentUser . GetUserId ( ) . Returns ( userId ) ;
213+
214+ var expectedExceptionMessage = "Current password is incorrect" ;
215+ _userService . ChangePasswordAsync ( command . Password , command . NewPassword , command . ConfirmNewPassword , userId . ToString ( ) )
216+ . ThrowsAsync ( new UnauthorizedAccessException ( expectedExceptionMessage ) ) ;
217+
218+ // Act & Assert
219+ var exception = await Should . ThrowAsync < UnauthorizedAccessException > (
220+ async ( ) => await _sut . Handle ( command , CancellationToken . None ) ) ;
221+
222+ exception . Message . ShouldBe ( expectedExceptionMessage ) ;
223+ }
224+
225+ [ Fact ]
226+ public async Task Handle_Should_ThrowException_When_GetUserIdThrows ( )
227+ {
228+ // Arrange
229+ var command = new ChangePasswordCommand
230+ {
231+ Password = "CurrentPassword123!" ,
232+ NewPassword = "NewPassword456!" ,
233+ ConfirmNewPassword = "NewPassword456!"
234+ } ;
235+
236+ _currentUser . IsAuthenticated ( ) . Returns ( true ) ;
237+ _currentUser . GetUserId ( ) . Throws ( new InvalidOperationException ( "User ID not available" ) ) ;
238+
239+ // Act & Assert
240+ await Should . ThrowAsync < InvalidOperationException > (
241+ async ( ) => await _sut . Handle ( command , CancellationToken . None ) ) ;
242+ }
243+
244+ #endregion
245+
246+ #region Handle - Null Command Tests
247+
248+ [ Fact ]
249+ public async Task Handle_Should_ThrowArgumentNullException_When_CommandIsNull ( )
250+ {
251+ // Act & Assert
252+ await Should . ThrowAsync < ArgumentNullException > ( async ( ) =>
253+ await _sut . Handle ( null ! , CancellationToken . None ) ) ;
254+ }
255+
256+ #endregion
257+
258+ #region Handle - CancellationToken Tests
259+
260+ [ Fact ]
261+ public async Task Handle_Should_HandleCancellationToken_Properly ( )
262+ {
263+ // Arrange
264+ var command = new ChangePasswordCommand
265+ {
266+ Password = "CurrentPassword123!" ,
267+ NewPassword = "NewPassword456!" ,
268+ ConfirmNewPassword = "NewPassword456!"
269+ } ;
270+
271+ var userId = _fixture . Create < Guid > ( ) ;
272+ using var cts = new CancellationTokenSource ( ) ;
273+ var cancellationToken = cts . Token ;
274+
275+ _currentUser . IsAuthenticated ( ) . Returns ( true ) ;
276+ _currentUser . GetUserId ( ) . Returns ( userId ) ;
277+
278+ _userService . ChangePasswordAsync ( command . Password , command . NewPassword , command . ConfirmNewPassword , userId . ToString ( ) )
279+ . Returns ( Task . CompletedTask ) ;
280+
281+ // Act
282+ var result = await _sut . Handle ( command , cancellationToken ) ;
283+
284+ // Assert
285+ result . ShouldBe ( "password reset email sent" ) ;
286+ }
287+
288+ #endregion
289+
290+ #region Handle - Edge Cases
291+
292+ [ Fact ]
293+ public async Task Handle_Should_HandleEmptyPasswords_When_ProvidedInCommand ( )
294+ {
295+ // Arrange
296+ var command = new ChangePasswordCommand
297+ {
298+ Password = "" ,
299+ NewPassword = "" ,
300+ ConfirmNewPassword = ""
301+ } ;
302+
303+ var userId = _fixture . Create < Guid > ( ) ;
304+
305+ _currentUser . IsAuthenticated ( ) . Returns ( true ) ;
306+ _currentUser . GetUserId ( ) . Returns ( userId ) ;
307+
308+ _userService . ChangePasswordAsync ( Arg . Any < string > ( ) , Arg . Any < string > ( ) , Arg . Any < string > ( ) , Arg . Any < string > ( ) )
309+ . Returns ( Task . CompletedTask ) ;
310+
311+ // Act
312+ var result = await _sut . Handle ( command , CancellationToken . None ) ;
313+
314+ // Assert
315+ result . ShouldBe ( "password reset email sent" ) ;
316+ await _userService . Received ( 1 ) . ChangePasswordAsync ( "" , "" , "" , userId . ToString ( ) ) ;
317+ }
318+
319+ #endregion
320+ }
0 commit comments