Skip to content

Commit da56519

Browse files
Merge branch 'main' into 89-backend---add-new-endpoints-for-comment-functionality
2 parents 7a1fe29 + 9e03ba6 commit da56519

33 files changed

+274
-179
lines changed

exercise.tests/Helpers/UserTestCases.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
1-
using NUnit.Framework;
2-
using System.Collections.Generic;
3-
41
namespace exercise.tests.Helpers
52
{
3+
/// <summary>
4+
/// Provides reusable user-centric <see cref="TestCaseData"/> scenarios for integration tests.
5+
/// </summary>
66
public static class UserTestCases
77
{
8+
/// <summary>
9+
/// Supplies registration payloads that should produce successful responses.
10+
/// </summary>
811
public static IEnumerable<TestCaseData> ValidRegisterCases()
912
{
1013
yield return new TestCaseData("validuser", "valid@email.com", "ValidPass1!");
1114
yield return new TestCaseData("user-name", "user1@example.com", "SecurePass9#");
1215
yield return new TestCaseData("user1name", "user2@example.com", "StrongPass$1");
1316
}
1417

18+
/// <summary>
19+
/// Supplies registration payloads that should fail validation.
20+
/// </summary>
1521
public static IEnumerable<TestCaseData> InvalidRegisterCases()
1622
{
1723
yield return new TestCaseData("validuser", "plainaddress", "ValidPass1!").SetName("Invalid: Invalid email format (register)");
@@ -22,6 +28,9 @@ public static IEnumerable<TestCaseData> InvalidRegisterCases()
2228
yield return new TestCaseData("validuser", "valid@email.com", "NoSpecialChar1").SetName("Invalid: Missing special character (register)");
2329
}
2430

31+
/// <summary>
32+
/// Supplies credential pairs that represent successful login attempts.
33+
/// </summary>
2534
public static IEnumerable<TestCaseData> ValidLoginCases()
2635
{
2736
yield return new TestCaseData("oyvind.perez1@example.com", "SuperHash!4");
@@ -30,6 +39,9 @@ public static IEnumerable<TestCaseData> ValidLoginCases()
3039
//yield return new TestCaseData("nigel.nowak2@example.com", "ValidPass1!");
3140
}
3241

42+
/// <summary>
43+
/// Supplies credential pairs that should fail authentication.
44+
/// </summary>
3345
public static IEnumerable<TestCaseData> InvalidLoginCases()
3446
{
3547
yield return new TestCaseData("valid@email.com", "short1!").SetName("Invalid: Password too short (login)");
@@ -40,4 +52,4 @@ public static IEnumerable<TestCaseData> InvalidLoginCases()
4052
yield return new TestCaseData("user@domain.c", "ValidPass1!").SetName("Invalid: Invalid email domain (login)");
4153
}
4254
}
43-
}
55+
}

exercise.tests/Helpers/UsernameValidationCases.cs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
using NUnit.Framework;
2-
using System;
3-
using System.Collections.Generic;
4-
using System.Net;
1+
using System.Net;
52

63
namespace exercise.tests.Helpers
74
{
5+
/// <summary>
6+
/// Centralises username validation scenarios for reuse across integration tests.
7+
/// </summary>
88
public static class UsernameValidationTestData
99
{
10+
/// <summary>
11+
/// Yields username samples alongside the expected message outcome for each endpoint.
12+
/// </summary>
1013
public static IEnumerable<TestCaseData> UsernameValidationMessageCases()
11-
{
14+
{
1215
string[] endpoints = new string[]
1316
{
1417
"username",
@@ -56,6 +59,9 @@ public static IEnumerable<TestCaseData> UsernameValidationMessageCases()
5659
}
5760

5861
}
62+
/// <summary>
63+
/// Yields username samples alongside the expected status code for each endpoint.
64+
/// </summary>
5965
public static IEnumerable<TestCaseData> UsernameValidationStatusCases()
6066
{
6167
string[] endpoints = new string[]
@@ -103,6 +109,6 @@ public static IEnumerable<TestCaseData> UsernameValidationStatusCases()
103109
}
104110
}
105111
}
106-
112+
107113
}
108114
}

exercise.tests/IntegrationTests/EndpointStatusTest.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
using exercise.wwwapi.DTOs.Login;
2-
using exercise.wwwapi.DTOs.Register;
3-
using Microsoft.AspNetCore.Http;
4-
using Microsoft.AspNetCore.Mvc.Testing;
1+
using Microsoft.AspNetCore.Mvc.Testing;
52
using System.Net;
63
using System.Text;
74
using System.Text.Json;
85
using System.Text.Json.Nodes;
96

107
namespace exercise.tests.IntegrationTests
118
{
9+
/// <summary>
10+
/// Integration smoke tests that ensure unexpected endpoints return consistent error responses.
11+
/// </summary>
1212
[TestFixture]
1313
public class EndpointStatusTest
1414
{
@@ -30,11 +30,15 @@ public void TearDown()
3030
_factory.Dispose();
3131
}
3232

33-
[Test]
33+
/// <summary>
34+
/// Verifies POST requests to unknown routes return a 404 with the canonical payload.
35+
/// </summary>
36+
[Test]
3437
public async Task PostCheckInvalidEndpoint()
3538
{
36-
var body = new {
37-
email= $"myemailVery@gmail.com",
39+
var body = new
40+
{
41+
email = $"myemailVery@gmail.com",
3842
password = "someR21!password"
3943
};
4044
var json = JsonSerializer.Serialize(body);
@@ -59,6 +63,9 @@ public async Task PostCheckInvalidEndpoint()
5963
Assert.That(message?["message"]?.GetValue<string>(), Is.EqualTo("Endpoint not found"));
6064
}
6165

66+
/// <summary>
67+
/// Verifies GET requests to unknown routes return a 404 with the canonical payload.
68+
/// </summary>
6269
[Test]
6370
public async Task GetCheckInvalidEndpoint()
6471
{

exercise.tests/IntegrationTests/PostTests.cs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
11
using exercise.wwwapi.DTOs.Login;
22
using exercise.wwwapi.DTOs.Posts;
3-
using exercise.wwwapi.Models;
4-
using Microsoft.AspNetCore.Http;
5-
using Microsoft.AspNetCore.Http.HttpResults;
63
using Microsoft.AspNetCore.Mvc.Testing;
7-
using Newtonsoft.Json.Linq;
8-
using System;
9-
using System.Collections.Generic;
10-
using System.ComponentModel.Design;
11-
using System.Linq;
124
using System.Net;
135
using System.Net.Http.Headers;
146
using System.Text;
157
using System.Text.Json;
168
using System.Text.Json.Nodes;
17-
using System.Threading.Tasks;
18-
using static Microsoft.ApplicationInsights.MetricDimensionNames.TelemetryContext;
199

2010
namespace exercise.tests.IntegrationTests
2111
{
12+
/// <summary>
13+
/// Integration tests covering the lifecycle of posts via the public API endpoints.
14+
/// </summary>
2215
[TestFixture]
2316
public class PostTests
2417
{

exercise.tests/IntegrationTests/UserTests.cs

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
using exercise.wwwapi.DTOs;
2-
using exercise.wwwapi.DTOs.Login;
1+
using exercise.wwwapi.DTOs.Login;
32
using exercise.wwwapi.DTOs.Register;
4-
using exercise.wwwapi.Models;
5-
using Microsoft.AspNetCore.Http;
63
using Microsoft.AspNetCore.Mvc.Testing;
74
using System.Net;
85
using System.Text;
@@ -12,6 +9,9 @@
129

1310
namespace exercise.tests.IntegrationTests
1411
{
12+
/// <summary>
13+
/// Integration tests exercising the user management endpoints end-to-end via the API surface.
14+
/// </summary>
1515
[TestFixture]
1616
public class UserTests
1717
{
@@ -33,15 +33,22 @@ public void TearDown()
3333
_factory.Dispose();
3434
}
3535

36-
// ad test cases for approved usernames, emails
37-
[Test, TestCaseSource(typeof(UserTestCases), nameof(UserTestCases.ValidRegisterCases))]
36+
/// <summary>
37+
/// Confirms that valid registration payloads yield an HTTP 201 Created response.
38+
/// </summary>
39+
/// <remarks>Test cases come from <see cref="UserTestCases.ValidRegisterCases"/> and are uniquified at runtime to avoid clashes.</remarks>
40+
/// <param name="username">The base username supplied by the data source.</param>
41+
/// <param name="email">The base email supplied by the data source.</param>
42+
/// <param name="password">The password candidate to register with.</param>
43+
[Test, TestCaseSource(typeof(UserTestCases), nameof(UserTestCases.ValidRegisterCases))]
3844
public async Task Register_success(string username, string email, string password)
3945
{
4046
var uniqueId = DateTime.UtcNow.ToString("yyMMddHHmmssffff");
4147

4248
string uniqueUsername = username.Length > 0 ? username + uniqueId : "";
4349

44-
RegisterRequestDTO body = new RegisterRequestDTO {
50+
RegisterRequestDTO body = new RegisterRequestDTO
51+
{
4552
email = $"{uniqueId}{email}",
4653
password = password
4754
};
@@ -64,6 +71,13 @@ public async Task Register_success(string username, string email, string passwor
6471
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.Created));
6572
}
6673

74+
/// <summary>
75+
/// Verifies the registration endpoint rejects malformed payloads with non-Created responses.
76+
/// </summary>
77+
/// <remarks>Inputs are provided by <see cref="UserTestCases.InvalidRegisterCases"/>.</remarks>
78+
/// <param name="username">The attempted username for registration.</param>
79+
/// <param name="email">The attempted email for registration.</param>
80+
/// <param name="password">The password candidate under test.</param>
6781
[Test, TestCaseSource(typeof(UserTestCases), nameof(UserTestCases.InvalidRegisterCases))]
6882
public async Task Register_Failure(string username, string email, string password)
6983
{
@@ -77,7 +91,7 @@ public async Task Register_Failure(string username, string email, string passwor
7791
email = $"{uniqueId}{email}",
7892
password = password
7993
};
80-
94+
8195
var json = JsonSerializer.Serialize(body);
8296
var requestBody = new StringContent(json, Encoding.UTF8, "application/json");
8397

@@ -98,6 +112,12 @@ public async Task Register_Failure(string username, string email, string passwor
98112
}
99113

100114

115+
/// <summary>
116+
/// Ensures valid credentials receive an HTTP 200 response and a token payload.
117+
/// </summary>
118+
/// <remarks>Credentials are sourced from <see cref="UserTestCases.ValidLoginCases"/>.</remarks>
119+
/// <param name="email">The email address to authenticate with.</param>
120+
/// <param name="password">The password paired with the supplied email.</param>
101121
[Test, TestCaseSource(typeof(UserTestCases), nameof(UserTestCases.ValidLoginCases))]
102122
public async Task Login_success(string email, string password)
103123
{
@@ -129,6 +149,12 @@ public async Task Login_success(string email, string password)
129149
Assert.That(message["data"]["token"], Is.Not.Null);
130150
}
131151

152+
/// <summary>
153+
/// Asserts that invalid login attempts fail with the expected HTTP status and message.
154+
/// </summary>
155+
/// <remarks>Negative credential combinations come from <see cref="UserTestCases.InvalidLoginCases"/>.</remarks>
156+
/// <param name="email">The incorrect email supplied to the login endpoint.</param>
157+
/// <param name="password">The incorrect password paired with the email.</param>
132158
[Test, TestCaseSource(typeof(UserTestCases), nameof(UserTestCases.InvalidLoginCases))]
133159
public async Task Login_failure(string email, string password)
134160
{
@@ -163,6 +189,9 @@ public async Task Login_failure(string email, string password)
163189

164190
}
165191

192+
/// <summary>
193+
/// Validates that a PATCH with complete, valid fields updates an existing user successfully.
194+
/// </summary>
166195
[Test]
167196
public async Task UpdateUserSuccess()
168197
{
@@ -183,20 +212,26 @@ public async Task UpdateUserSuccess()
183212
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.OK));
184213
}
185214

215+
/// <summary>
216+
/// Ensures PATCH requests with no mutable fields return a bad request response.
217+
/// </summary>
186218
[Test]
187-
public async Task UpdateUserNoContent()
219+
public async Task UpdateUserNullFieldsOnly()
188220
{
189-
var fieldsToUpdate = new Dictionary<string, object?>{};
221+
var fieldsToUpdate = new Dictionary<string, object?> { };
190222

191223
var json = JsonSerializer.Serialize(fieldsToUpdate);
192224
var content = new StringContent(json, Encoding.UTF8, "application/json");
193225

194226
int userId = 1;
195227
var response = await _client.PatchAsync($"/users/{userId}", content);
196228

197-
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.NoContent));
229+
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
198230
}
199231

232+
/// <summary>
233+
/// Confirms the API rejects usernames that violate allowed formatting rules.
234+
/// </summary>
200235
[Test]
201236
public async Task UpdateUserInvalidUsername()
202237
{
@@ -214,6 +249,9 @@ public async Task UpdateUserInvalidUsername()
214249
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
215250
}
216251

252+
/// <summary>
253+
/// Confirms GitHub username updates honor the same validation rules as the standalone validator.
254+
/// </summary>
217255
[Test]
218256
public async Task UpdateUserInvalidGitHubUsername()
219257
{
@@ -231,6 +269,9 @@ public async Task UpdateUserInvalidGitHubUsername()
231269
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
232270
}
233271

272+
/// <summary>
273+
/// Verifies email updates are blocked when the address fails format validation.
274+
/// </summary>
234275
[Test]
235276
public async Task UpdateUserInvalidEmail()
236277
{
@@ -248,6 +289,9 @@ public async Task UpdateUserInvalidEmail()
248289
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
249290
}
250291

292+
/// <summary>
293+
/// Confirms passwords that do not meet the complexity requirements are rejected on update.
294+
/// </summary>
251295
[Test]
252296
public async Task UpdateUserInvalidPassword()
253297
{
@@ -265,6 +309,9 @@ public async Task UpdateUserInvalidPassword()
265309
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
266310
}
267311

312+
/// <summary>
313+
/// Ensures role updates outside the defined enum range produce a bad request.
314+
/// </summary>
268315
[Test]
269316
public async Task UpdateUserInvalidRole()
270317
{
@@ -282,6 +329,9 @@ public async Task UpdateUserInvalidRole()
282329
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
283330
}
284331

332+
/// <summary>
333+
/// Verifies the API prevents changing a username to one that is already in use.
334+
/// </summary>
285335
[Test]
286336
public async Task UpdateUserUsernameExists()
287337
{
@@ -299,6 +349,9 @@ public async Task UpdateUserUsernameExists()
299349
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
300350
}
301351

352+
/// <summary>
353+
/// Verifies the API prevents reusing an existing GitHub username value.
354+
/// </summary>
302355
[Test]
303356
public async Task UpdateUserGitHubUsernameExists()
304357
{
@@ -316,6 +369,9 @@ public async Task UpdateUserGitHubUsernameExists()
316369
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
317370
}
318371

372+
/// <summary>
373+
/// Verifies the API prevents reusing an existing email address value.
374+
/// </summary>
319375
[Test]
320376
public async Task UpdateUserEmailExists()
321377
{
@@ -333,6 +389,11 @@ public async Task UpdateUserEmailExists()
333389
Assert.That(response.StatusCode, Is.EqualTo(HttpStatusCode.BadRequest));
334390
}
335391

392+
/// <summary>
393+
/// Ensures retrieving users by id returns 200 for known users and 404 for missing ones.
394+
/// </summary>
395+
/// <param name="id">The user id sent to the endpoint.</param>
396+
/// <param name="responseStatus">The expected HTTP status code.</param>
336397
[TestCase("1", HttpStatusCode.OK)]
337398
[TestCase("10000000", HttpStatusCode.NotFound)]
338399
public async Task GetUserByIdTest(string id, HttpStatusCode responseStatus)

0 commit comments

Comments
 (0)