mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
Add authenticated user profile page with locked-down account endpoint (#315)
This commit is contained in:
@@ -167,4 +167,40 @@ public class ApiGeneralSteps(ScenarioContext scenario)
|
||||
actualValue
|
||||
);
|
||||
}
|
||||
|
||||
[Then("the response JSON should not contain {string}")]
|
||||
public void ThenTheResponseJsonShouldNotContainString(string field)
|
||||
{
|
||||
scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
|
||||
|
||||
using JsonDocument doc = JsonDocument.Parse(responseBody!);
|
||||
JsonElement root = doc.RootElement;
|
||||
|
||||
AssertFieldAbsent(root, field);
|
||||
|
||||
if (root.ValueKind == JsonValueKind.Object &&
|
||||
root.TryGetProperty("payload", out JsonElement payloadElem))
|
||||
AssertFieldAbsent(payloadElem, field);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recurses into arrays so a listing endpoint's response can be asserted the same way as a
|
||||
/// single-resource response.
|
||||
/// </summary>
|
||||
private static void AssertFieldAbsent(JsonElement element, string field)
|
||||
{
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
element
|
||||
.TryGetProperty(field, out _)
|
||||
.Should()
|
||||
.BeFalse("Expected field '{0}' to be absent from the response", field);
|
||||
break;
|
||||
case JsonValueKind.Array:
|
||||
foreach (JsonElement item in element.EnumerateArray())
|
||||
AssertFieldAbsent(item, field);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,6 +347,46 @@ public class AuthSteps(ScenarioContext scenario) : ApiStepsBase(scenario)
|
||||
Scenario[RegisteredUsernameKey] = ParseRegisteredUsername(root);
|
||||
}
|
||||
|
||||
[Given("I am logged in as a different user")]
|
||||
public async Task GivenIAmLoggedInAsADifferentUser()
|
||||
{
|
||||
HttpClient client = GetClient();
|
||||
string suffix = Guid.NewGuid().ToString("N")[..8];
|
||||
var registrationData = new
|
||||
{
|
||||
username = $"otheruser-{suffix}",
|
||||
firstName = "Other",
|
||||
lastName = "User",
|
||||
email = $"otheruser-{suffix}@example.com",
|
||||
dateOfBirth = "1990-01-01",
|
||||
password = "Password1!",
|
||||
};
|
||||
|
||||
HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/register")
|
||||
{
|
||||
Content = new StringContent(
|
||||
JsonSerializer.Serialize(registrationData),
|
||||
Encoding.UTF8,
|
||||
"application/json"
|
||||
),
|
||||
};
|
||||
|
||||
HttpResponseMessage response = await client.SendAsync(requestMessage);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
using JsonDocument doc = JsonDocument.Parse(responseBody);
|
||||
JsonElement payload = doc.RootElement.GetProperty("payload");
|
||||
string accessToken =
|
||||
(
|
||||
payload.TryGetProperty("accessToken", out JsonElement tokenElem)
|
||||
? tokenElem.GetString()
|
||||
: null
|
||||
) ?? throw new InvalidOperationException("accessToken missing from registration payload");
|
||||
|
||||
Scenario["accessToken"] = accessToken;
|
||||
}
|
||||
|
||||
[Given("I am logged in")]
|
||||
public async Task GivenIAmLoggedIn()
|
||||
{
|
||||
|
||||
@@ -262,46 +262,6 @@ public class BrewerySteps(ScenarioContext scenario) : ApiStepsBase(scenario)
|
||||
await SendAsync(requestMessage);
|
||||
}
|
||||
|
||||
[Given("I am logged in as a different user")]
|
||||
public async Task GivenIAmLoggedInAsADifferentUser()
|
||||
{
|
||||
HttpClient client = GetClient();
|
||||
string suffix = Guid.NewGuid().ToString("N")[..8];
|
||||
var registrationData = new
|
||||
{
|
||||
username = $"otheruser-{suffix}",
|
||||
firstName = "Other",
|
||||
lastName = "User",
|
||||
email = $"otheruser-{suffix}@example.com",
|
||||
dateOfBirth = "1990-01-01",
|
||||
password = "Password1!",
|
||||
};
|
||||
|
||||
HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/register")
|
||||
{
|
||||
Content = new StringContent(
|
||||
JsonSerializer.Serialize(registrationData),
|
||||
Encoding.UTF8,
|
||||
"application/json"
|
||||
),
|
||||
};
|
||||
|
||||
HttpResponseMessage response = await client.SendAsync(requestMessage);
|
||||
response.EnsureSuccessStatusCode();
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
using JsonDocument doc = JsonDocument.Parse(responseBody);
|
||||
JsonElement payload = doc.RootElement.GetProperty("payload");
|
||||
string accessToken =
|
||||
(
|
||||
payload.TryGetProperty("accessToken", out JsonElement tokenElem)
|
||||
? tokenElem.GetString()
|
||||
: null
|
||||
) ?? throw new InvalidOperationException("accessToken missing from registration payload");
|
||||
|
||||
Scenario[AccessTokenKey] = accessToken;
|
||||
}
|
||||
|
||||
[Then("retrieving the brewery by ID should now return HTTP status {int}")]
|
||||
public async Task ThenRetrievingTheBreweryByIdShouldNowReturnHttpStatus(int expectedCode)
|
||||
{
|
||||
|
||||
95
web/backend/API/API.Specs/Steps/UserSteps.cs
Normal file
95
web/backend/API/API.Specs/Steps/UserSteps.cs
Normal file
@@ -0,0 +1,95 @@
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using Reqnroll;
|
||||
|
||||
namespace API.Specs.Steps;
|
||||
|
||||
[Binding]
|
||||
public class UserSteps(ScenarioContext scenario) : ApiStepsBase(scenario)
|
||||
{
|
||||
private const string AccessTokenKey = "accessToken";
|
||||
private const string RegisteredUserIdKey = "registeredUserId";
|
||||
private const string RegisteredUsernameKey = "registeredUsername";
|
||||
|
||||
private async Task SendAsync(HttpRequestMessage requestMessage)
|
||||
{
|
||||
HttpClient client = GetClient();
|
||||
HttpResponseMessage response = await client.SendAsync(requestMessage);
|
||||
string responseBody = await response.Content.ReadAsStringAsync();
|
||||
|
||||
Scenario[ResponseKey] = response;
|
||||
Scenario[ResponseBodyKey] = responseBody;
|
||||
}
|
||||
|
||||
private HttpRequestMessage NewAuthenticatedRequest(HttpMethod method, string url)
|
||||
{
|
||||
HttpRequestMessage requestMessage = new(method, url);
|
||||
if (Scenario.TryGetValue<string>(AccessTokenKey, out string? accessToken))
|
||||
requestMessage.Headers.Add("Authorization", $"Bearer {accessToken}");
|
||||
return requestMessage;
|
||||
}
|
||||
|
||||
private Guid GetRegisteredUserId()
|
||||
{
|
||||
return Scenario.TryGetValue(RegisteredUserIdKey, out Guid id)
|
||||
? id
|
||||
: throw new InvalidOperationException("registered user ID not found in scenario");
|
||||
}
|
||||
|
||||
[When("I retrieve the public profile by ID")]
|
||||
public async Task WhenIRetrieveThePublicProfileById()
|
||||
{
|
||||
await SendAsync(NewAuthenticatedRequest(HttpMethod.Get, $"/api/user/{GetRegisteredUserId()}/profile"));
|
||||
}
|
||||
|
||||
[When("I retrieve the public profile by ID without authentication")]
|
||||
public async Task WhenIRetrieveThePublicProfileByIdWithoutAuthentication()
|
||||
{
|
||||
await SendAsync(new HttpRequestMessage(HttpMethod.Get, $"/api/user/{GetRegisteredUserId()}/profile"));
|
||||
}
|
||||
|
||||
[When("I retrieve the public profile by a non-existent ID")]
|
||||
public async Task WhenIRetrieveThePublicProfileByANonExistentId()
|
||||
{
|
||||
await SendAsync(NewAuthenticatedRequest(HttpMethod.Get, $"/api/user/{Guid.NewGuid()}/profile"));
|
||||
}
|
||||
|
||||
[When("I retrieve the user account by a non-existent ID without authentication")]
|
||||
public async Task WhenIRetrieveTheUserAccountByANonExistentIdWithoutAuthentication()
|
||||
{
|
||||
await SendAsync(new HttpRequestMessage(HttpMethod.Get, $"/api/user/{Guid.NewGuid()}"));
|
||||
}
|
||||
|
||||
[When("I list user accounts without authentication")]
|
||||
public async Task WhenIListUserAccountsWithoutAuthentication()
|
||||
{
|
||||
await SendAsync(new HttpRequestMessage(HttpMethod.Get, "/api/user"));
|
||||
}
|
||||
|
||||
[When("I list user accounts")]
|
||||
public async Task WhenIListUserAccounts()
|
||||
{
|
||||
await SendAsync(NewAuthenticatedRequest(HttpMethod.Get, "/api/user"));
|
||||
}
|
||||
|
||||
[When("I retrieve the registered account by ID")]
|
||||
public async Task WhenIRetrieveTheRegisteredAccountById()
|
||||
{
|
||||
await SendAsync(NewAuthenticatedRequest(HttpMethod.Get, $"/api/user/{GetRegisteredUserId()}"));
|
||||
}
|
||||
|
||||
[Then("the public profile response should match the registered account")]
|
||||
public void ThenThePublicProfileResponseShouldMatchTheRegisteredAccount()
|
||||
{
|
||||
Scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
|
||||
string? username = Scenario.TryGetValue<string>(RegisteredUsernameKey, out string? u)
|
||||
? u
|
||||
: throw new InvalidOperationException("registered username not found in scenario");
|
||||
|
||||
using JsonDocument doc = JsonDocument.Parse(responseBody!);
|
||||
JsonElement root = doc.RootElement;
|
||||
|
||||
root.GetProperty("userAccountId").GetGuid().Should().Be(GetRegisteredUserId());
|
||||
root.GetProperty("username").GetString().Should().Be(username);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user