Skip to content

Commit acebf4c

Browse files
committed
test: Add unit tests to reach 50% code coverage
Adds 89 new tests bringing total from 12 to 101. Coverage went from 44% to 52%. New test coverage: - AuthService: login flow, account lockout, token refresh - DocumentService: pagination, delete, batch generation - TemplateService: full CRUD operations - ExceptionHandlingMiddleware: all exception type mappings - Validators: RegisterDto, LoginDto, CreateTemplateDto, etc. - PaginatedResult: edge cases for pagination logic - AuthController & ControllerExtensions: basic unit tests Also adds coverage/ to .gitignore since test runs generate reports there.
1 parent e794e50 commit acebf4c

File tree

9 files changed

+1707
-0
lines changed

9 files changed

+1707
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ obj/
99
*.db-wal
1010
GeneratedDocuments/
1111
TestResults/
12+
coverage/
1213

1314
# TUI Log Files
1415
*.log
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
using DocumentGenerator.API.Controllers;
2+
using DocumentGenerator.Core.DTOs;
3+
using DocumentGenerator.Core.Interfaces;
4+
using FluentAssertions;
5+
using Microsoft.AspNetCore.Mvc;
6+
using Moq;
7+
using Xunit;
8+
9+
namespace DocumentGenerator.Tests.Unit
10+
{
11+
public class AuthControllerTests
12+
{
13+
private readonly Mock<IAuthService> _mockAuthService;
14+
private readonly AuthController _controller;
15+
16+
public AuthControllerTests()
17+
{
18+
_mockAuthService = new Mock<IAuthService>();
19+
_controller = new AuthController(_mockAuthService.Object);
20+
}
21+
22+
[Fact]
23+
public async Task Register_ShouldReturnOk_WithAuthResponse()
24+
{
25+
// Arrange
26+
var registerDto = new RegisterDto
27+
{
28+
Username = "testuser",
29+
Email = "test@example.com",
30+
Password = "Password123!"
31+
};
32+
var authResponse = new AuthResponseDto
33+
{
34+
Token = "jwt-token",
35+
RefreshToken = "refresh-token",
36+
Expiration = DateTime.UtcNow.AddHours(1)
37+
};
38+
_mockAuthService.Setup(s => s.RegisterAsync(registerDto)).ReturnsAsync(authResponse);
39+
40+
// Act
41+
var result = await _controller.Register(registerDto);
42+
43+
// Assert
44+
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
45+
okResult.Value.Should().Be(authResponse);
46+
}
47+
48+
[Fact]
49+
public async Task Login_ShouldReturnOk_WithAuthResponse()
50+
{
51+
// Arrange
52+
var loginDto = new LoginDto
53+
{
54+
Username = "testuser",
55+
Password = "Password123!"
56+
};
57+
var authResponse = new AuthResponseDto
58+
{
59+
Token = "jwt-token",
60+
RefreshToken = "refresh-token",
61+
Expiration = DateTime.UtcNow.AddHours(1)
62+
};
63+
_mockAuthService.Setup(s => s.LoginAsync(loginDto)).ReturnsAsync(authResponse);
64+
65+
// Act
66+
var result = await _controller.Login(loginDto);
67+
68+
// Assert
69+
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
70+
okResult.Value.Should().Be(authResponse);
71+
}
72+
73+
[Fact]
74+
public async Task RefreshToken_ShouldReturnOk_WithAuthResponse()
75+
{
76+
// Arrange
77+
var refreshDto = new RefreshTokenDto
78+
{
79+
Token = "old-jwt-token",
80+
RefreshToken = "old-refresh-token"
81+
};
82+
var authResponse = new AuthResponseDto
83+
{
84+
Token = "new-jwt-token",
85+
RefreshToken = "new-refresh-token",
86+
Expiration = DateTime.UtcNow.AddHours(1)
87+
};
88+
_mockAuthService.Setup(s => s.RefreshTokenAsync(refreshDto.Token, refreshDto.RefreshToken))
89+
.ReturnsAsync(authResponse);
90+
91+
// Act
92+
var result = await _controller.RefreshToken(refreshDto);
93+
94+
// Assert
95+
var okResult = result.Result.Should().BeOfType<OkObjectResult>().Subject;
96+
okResult.Value.Should().Be(authResponse);
97+
}
98+
}
99+
}

DocumentGenerator.Tests/Unit/AuthServiceTests.cs

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,236 @@ public async Task RegisterAsync_ShouldThrow_WhenUsernameExists()
9292
// Assert
9393
await act.Should().ThrowAsync<DuplicateUsernameException>();
9494
}
95+
96+
[Fact]
97+
public async Task RegisterAsync_ShouldThrow_WhenEmailExists()
98+
{
99+
// Arrange
100+
var existingUser = new User
101+
{
102+
Id = Guid.NewGuid(),
103+
Username = "existinguser",
104+
Email = "test@example.com",
105+
PasswordHash = "hash"
106+
};
107+
_context.Users.Add(existingUser);
108+
await _context.SaveChangesAsync();
109+
110+
var dto = new RegisterDto
111+
{
112+
Username = "newuser",
113+
Email = "test@example.com",
114+
Password = "Password123!"
115+
};
116+
117+
// Act
118+
Func<Task> act = async () => await _authService.RegisterAsync(dto);
119+
120+
// Assert
121+
await act.Should().ThrowAsync<DuplicateEmailException>();
122+
}
123+
124+
[Fact]
125+
public async Task LoginAsync_ShouldReturnTokens_WhenCredentialsValid()
126+
{
127+
// Arrange
128+
var registerDto = new RegisterDto
129+
{
130+
Username = "loginuser",
131+
Email = "login@example.com",
132+
Password = "Password123!"
133+
};
134+
await _authService.RegisterAsync(registerDto);
135+
136+
var loginDto = new LoginDto
137+
{
138+
Username = "loginuser",
139+
Password = "Password123!"
140+
};
141+
142+
// Act
143+
var result = await _authService.LoginAsync(loginDto);
144+
145+
// Assert
146+
result.Should().NotBeNull();
147+
result.Token.Should().NotBeNullOrEmpty();
148+
result.RefreshToken.Should().NotBeNullOrEmpty();
149+
}
150+
151+
[Fact]
152+
public async Task LoginAsync_ShouldThrow_WhenUserNotFound()
153+
{
154+
// Arrange
155+
var loginDto = new LoginDto
156+
{
157+
Username = "nonexistent",
158+
Password = "Password123!"
159+
};
160+
161+
// Act
162+
Func<Task> act = async () => await _authService.LoginAsync(loginDto);
163+
164+
// Assert
165+
await act.Should().ThrowAsync<InvalidCredentialsException>();
166+
}
167+
168+
[Fact]
169+
public async Task LoginAsync_ShouldThrow_WhenPasswordInvalid()
170+
{
171+
// Arrange
172+
var registerDto = new RegisterDto
173+
{
174+
Username = "testuser2",
175+
Email = "test2@example.com",
176+
Password = "Password123!"
177+
};
178+
await _authService.RegisterAsync(registerDto);
179+
180+
var loginDto = new LoginDto
181+
{
182+
Username = "testuser2",
183+
Password = "WrongPassword!"
184+
};
185+
186+
// Act
187+
Func<Task> act = async () => await _authService.LoginAsync(loginDto);
188+
189+
// Assert
190+
await act.Should().ThrowAsync<InvalidCredentialsException>();
191+
}
192+
193+
[Fact]
194+
public async Task LoginAsync_ShouldLockAccount_After5FailedAttempts()
195+
{
196+
// Arrange
197+
var registerDto = new RegisterDto
198+
{
199+
Username = "lockuser",
200+
Email = "lock@example.com",
201+
Password = "Password123!"
202+
};
203+
await _authService.RegisterAsync(registerDto);
204+
205+
var loginDto = new LoginDto
206+
{
207+
Username = "lockuser",
208+
Password = "WrongPassword!"
209+
};
210+
211+
// Act - Make 5 failed attempts
212+
for (int i = 0; i < 5; i++)
213+
{
214+
try { await _authService.LoginAsync(loginDto); } catch { }
215+
}
216+
217+
// Try a 6th time - should throw AccountLockedException
218+
Func<Task> act = async () => await _authService.LoginAsync(loginDto);
219+
220+
// Assert
221+
await act.Should().ThrowAsync<AccountLockedException>();
222+
}
223+
224+
[Fact]
225+
public async Task LoginAsync_ShouldResetFailedAttempts_OnSuccessfulLogin()
226+
{
227+
// Arrange
228+
var registerDto = new RegisterDto
229+
{
230+
Username = "resetuser",
231+
Email = "reset@example.com",
232+
Password = "Password123!"
233+
};
234+
await _authService.RegisterAsync(registerDto);
235+
236+
var wrongLoginDto = new LoginDto
237+
{
238+
Username = "resetuser",
239+
Password = "WrongPassword!"
240+
};
241+
242+
// Make a few failed attempts (but not enough to lock)
243+
for (int i = 0; i < 3; i++)
244+
{
245+
try { await _authService.LoginAsync(wrongLoginDto); } catch { }
246+
}
247+
248+
// Now login successfully
249+
var correctLoginDto = new LoginDto
250+
{
251+
Username = "resetuser",
252+
Password = "Password123!"
253+
};
254+
255+
// Act
256+
var result = await _authService.LoginAsync(correctLoginDto);
257+
258+
// Assert
259+
result.Should().NotBeNull();
260+
var user = await _context.Users.FirstAsync(u => u.Username == "resetuser");
261+
user.FailedLoginAttempts.Should().Be(0);
262+
}
263+
264+
[Fact]
265+
public async Task RefreshTokenAsync_ShouldReturnNewTokens_WhenRefreshTokenValid()
266+
{
267+
// Arrange
268+
var registerDto = new RegisterDto
269+
{
270+
Username = "refreshuser",
271+
Email = "refresh@example.com",
272+
Password = "Password123!"
273+
};
274+
var authResponse = await _authService.RegisterAsync(registerDto);
275+
276+
// Act
277+
var result = await _authService.RefreshTokenAsync(authResponse.Token, authResponse.RefreshToken);
278+
279+
// Assert
280+
result.Should().NotBeNull();
281+
result.Token.Should().NotBeNullOrEmpty();
282+
result.RefreshToken.Should().NotBeNullOrEmpty();
283+
}
284+
285+
[Fact]
286+
public async Task RefreshTokenAsync_ShouldThrow_WhenRefreshTokenInvalid()
287+
{
288+
// Arrange
289+
var registerDto = new RegisterDto
290+
{
291+
Username = "invalidrefresh",
292+
Email = "invalidrefresh@example.com",
293+
Password = "Password123!"
294+
};
295+
var authResponse = await _authService.RegisterAsync(registerDto);
296+
297+
// Act
298+
Func<Task> act = async () => await _authService.RefreshTokenAsync(authResponse.Token, "invalid-refresh-token");
299+
300+
// Assert
301+
await act.Should().ThrowAsync<InvalidRefreshTokenException>();
302+
}
303+
304+
[Fact]
305+
public async Task RefreshTokenAsync_ShouldThrow_WhenRefreshTokenExpired()
306+
{
307+
// Arrange
308+
var user = new User
309+
{
310+
Id = Guid.NewGuid(),
311+
Username = "expireduser",
312+
Email = "expired@example.com",
313+
PasswordHash = BCrypt.Net.BCrypt.HashPassword("Password123!"),
314+
RefreshToken = "expired-token",
315+
RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(-1) // Expired yesterday
316+
};
317+
_context.Users.Add(user);
318+
await _context.SaveChangesAsync();
319+
320+
// Act
321+
Func<Task> act = async () => await _authService.RefreshTokenAsync("some-token", "expired-token");
322+
323+
// Assert
324+
await act.Should().ThrowAsync<InvalidRefreshTokenException>();
325+
}
95326
}
96327
}

0 commit comments

Comments
 (0)