Format ./web/backend/API/API.Specs

This commit is contained in:
Aaron Po
2026-06-20 15:14:21 -04:00
parent db3ed3581d
commit 6f8e5dc722
6 changed files with 126 additions and 373 deletions

View File

@@ -8,40 +8,37 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0"/> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageReference Include="xunit" Version="2.9.2"/> <PackageReference Include="xunit" Version="2.9.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2"/> <PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="FluentAssertions" Version="6.9.0"/> <PackageReference Include="FluentAssertions" Version="6.9.0" />
<PackageReference Include="dbup" Version="5.0.41"/> <PackageReference Include="dbup" Version="5.0.41" />
<!-- Reqnroll core, xUnit adapter and code-behind generator --> <!-- Reqnroll core, xUnit adapter and code-behind generator -->
<PackageReference Include="Reqnroll" Version="3.3.3"/> <PackageReference Include="Reqnroll" Version="3.3.3" />
<PackageReference Include="Reqnroll.xUnit" Version="3.3.3"/> <PackageReference Include="Reqnroll.xUnit" Version="3.3.3" />
<PackageReference <PackageReference
Include="Reqnroll.Tools.MsBuild.Generation" Include="Reqnroll.Tools.MsBuild.Generation"
Version="3.3.3" Version="3.3.3"
PrivateAssets="all" PrivateAssets="all"
/> />
<!-- ASP.NET Core integration testing --> <!-- ASP.NET Core integration testing -->
<PackageReference <PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.1" />
Include="Microsoft.AspNetCore.Mvc.Testing"
Version="9.0.1"
/>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<!-- Ensure feature files are included in the project --> <!-- Ensure feature files are included in the project -->
<None Include="Features\**\*.feature"/> <None Include="Features\**\*.feature" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Using Include="Xunit"/> <Using Include="Xunit" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\API.Core\API.Core.csproj"/> <ProjectReference Include="..\API.Core\API.Core.csproj" />
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email\Infrastructure.Email.csproj"/> <ProjectReference Include="..\..\Infrastructure\Infrastructure.Email\Infrastructure.Email.csproj" />
<ProjectReference Include="..\..\Features\Features.Emails\Features.Emails.csproj"/> <ProjectReference Include="..\..\Features\Features.Emails\Features.Emails.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -8,11 +8,7 @@ public class MockEmailDispatcher : IEmailDispatcher
public List<ResendConfirmationEmail> SentResendConfirmationEmails { get; } = new(); public List<ResendConfirmationEmail> SentResendConfirmationEmails { get; } = new();
public Task SendRegistrationEmailAsync( public Task SendRegistrationEmailAsync(string firstName, string email, string confirmationToken)
string firstName,
string email,
string confirmationToken
)
{ {
SentRegistrationEmails.Add( SentRegistrationEmails.Add(
new RegistrationEmail new RegistrationEmail
@@ -20,7 +16,7 @@ public class MockEmailDispatcher : IEmailDispatcher
FirstName = firstName, FirstName = firstName,
Email = email, Email = email,
ConfirmationToken = confirmationToken, ConfirmationToken = confirmationToken,
SentAt = DateTime.UtcNow SentAt = DateTime.UtcNow,
} }
); );
@@ -39,7 +35,7 @@ public class MockEmailDispatcher : IEmailDispatcher
FirstName = firstName, FirstName = firstName,
Email = email, Email = email,
ConfirmationToken = confirmationToken, ConfirmationToken = confirmationToken,
SentAt = DateTime.UtcNow SentAt = DateTime.UtcNow,
} }
); );

View File

@@ -10,12 +10,7 @@ public class MockEmailProvider : IEmailProvider
{ {
public List<SentEmail> SentEmails { get; } = new(); public List<SentEmail> SentEmails { get; } = new();
public Task SendAsync( public Task SendAsync(string to, string subject, string body, bool isHtml = true)
string to,
string subject,
string body,
bool isHtml = true
)
{ {
SentEmails.Add( SentEmails.Add(
new SentEmail new SentEmail
@@ -24,19 +19,14 @@ public class MockEmailProvider : IEmailProvider
Subject = subject, Subject = subject,
Body = body, Body = body,
IsHtml = isHtml, IsHtml = isHtml,
SentAt = DateTime.UtcNow SentAt = DateTime.UtcNow,
} }
); );
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task SendAsync( public Task SendAsync(IEnumerable<string> to, string subject, string body, bool isHtml = true)
IEnumerable<string> to,
string subject,
string body,
bool isHtml = true
)
{ {
SentEmails.Add( SentEmails.Add(
new SentEmail new SentEmail
@@ -45,7 +35,7 @@ public class MockEmailProvider : IEmailProvider
Subject = subject, Subject = subject,
Body = body, Body = body,
IsHtml = isHtml, IsHtml = isHtml,
SentAt = DateTime.UtcNow SentAt = DateTime.UtcNow,
} }
); );

View File

@@ -15,7 +15,8 @@ public class ApiGeneralSteps(ScenarioContext scenario)
private HttpClient GetClient() private HttpClient GetClient()
{ {
if (scenario.TryGetValue<HttpClient>(ClientKey, out HttpClient? client)) return client; if (scenario.TryGetValue<HttpClient>(ClientKey, out HttpClient? client))
return client;
TestApiFactory? factory = scenario.TryGetValue<TestApiFactory>( TestApiFactory? factory = scenario.TryGetValue<TestApiFactory>(
FactoryKey, FactoryKey,
@@ -47,11 +48,7 @@ public class ApiGeneralSteps(ScenarioContext scenario)
HttpRequestMessage requestMessage = new(new HttpMethod(method), url) HttpRequestMessage requestMessage = new(new HttpMethod(method), url)
{ {
Content = new StringContent( Content = new StringContent(jsonBody, Encoding.UTF8, "application/json"),
jsonBody,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -62,16 +59,10 @@ public class ApiGeneralSteps(ScenarioContext scenario)
} }
[When("I send an HTTP request {string} to {string}")] [When("I send an HTTP request {string} to {string}")]
public async Task WhenISendAnHttpRequestStringToString( public async Task WhenISendAnHttpRequestStringToString(string method, string url)
string method,
string url
)
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(new HttpMethod(method), url);
new HttpMethod(method),
url
);
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
string responseBody = await response.Content.ReadAsStringAsync(); string responseBody = await response.Content.ReadAsStringAsync();
@@ -100,19 +91,13 @@ public class ApiGeneralSteps(ScenarioContext scenario)
} }
[Then("the response JSON should have {string} equal {string}")] [Then("the response JSON should have {string} equal {string}")]
public void ThenTheResponseJsonShouldHaveStringEqualString( public void ThenTheResponseJsonShouldHaveStringEqualString(string field, string expected)
string field,
string expected
)
{ {
scenario scenario
.TryGetValue<HttpResponseMessage>(ResponseKey, out HttpResponseMessage? response) .TryGetValue<HttpResponseMessage>(ResponseKey, out HttpResponseMessage? response)
.Should() .Should()
.BeTrue(); .BeTrue();
scenario scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
.TryGetValue<string>(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
using JsonDocument doc = JsonDocument.Parse(responseBody!); using JsonDocument doc = JsonDocument.Parse(responseBody!);
JsonElement root = doc.RootElement; JsonElement root = doc.RootElement;
@@ -125,25 +110,16 @@ public class ApiGeneralSteps(ScenarioContext scenario)
"Expected field '{0}' to be present either at the root or inside 'payload'", "Expected field '{0}' to be present either at the root or inside 'payload'",
field field
); );
payloadElem payloadElem.ValueKind.Should().Be(JsonValueKind.Object, "payload must be an object");
.ValueKind.Should()
.Be(JsonValueKind.Object, "payload must be an object");
payloadElem payloadElem
.TryGetProperty(field, out value) .TryGetProperty(field, out value)
.Should() .Should()
.BeTrue( .BeTrue("Expected field '{0}' to be present inside 'payload'", field);
"Expected field '{0}' to be present inside 'payload'",
field
);
} }
value value
.ValueKind.Should() .ValueKind.Should()
.Be( .Be(JsonValueKind.String, "Expected field '{0}' to be a string", field);
JsonValueKind.String,
"Expected field '{0}' to be a string",
field
);
value.GetString().Should().Be(expected); value.GetString().Should().Be(expected);
} }
@@ -157,10 +133,7 @@ public class ApiGeneralSteps(ScenarioContext scenario)
.TryGetValue<HttpResponseMessage>(ResponseKey, out HttpResponseMessage? response) .TryGetValue<HttpResponseMessage>(ResponseKey, out HttpResponseMessage? response)
.Should() .Should()
.BeTrue(); .BeTrue();
scenario scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
.TryGetValue<string>(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
using JsonDocument doc = JsonDocument.Parse(responseBody!); using JsonDocument doc = JsonDocument.Parse(responseBody!);
JsonElement root = doc.RootElement; JsonElement root = doc.RootElement;
@@ -173,25 +146,16 @@ public class ApiGeneralSteps(ScenarioContext scenario)
"Expected field '{0}' to be present either at the root or inside 'payload'", "Expected field '{0}' to be present either at the root or inside 'payload'",
field field
); );
payloadElem payloadElem.ValueKind.Should().Be(JsonValueKind.Object, "payload must be an object");
.ValueKind.Should()
.Be(JsonValueKind.Object, "payload must be an object");
payloadElem payloadElem
.TryGetProperty(field, out value) .TryGetProperty(field, out value)
.Should() .Should()
.BeTrue( .BeTrue("Expected field '{0}' to be present inside 'payload'", field);
"Expected field '{0}' to be present inside 'payload'",
field
);
} }
value value
.ValueKind.Should() .ValueKind.Should()
.Be( .Be(JsonValueKind.String, "Expected field '{0}' to be a string", field);
JsonValueKind.String,
"Expected field '{0}' to be a string",
field
);
string? actualValue = value.GetString(); string? actualValue = value.GetString();
actualValue actualValue
.Should() .Should()

View File

@@ -21,7 +21,8 @@ public class AuthSteps(ScenarioContext scenario)
private HttpClient GetClient() private HttpClient GetClient()
{ {
if (scenario.TryGetValue<HttpClient>(ClientKey, out HttpClient? client)) return client; if (scenario.TryGetValue<HttpClient>(ClientKey, out HttpClient? client))
return client;
TestApiFactory? factory = scenario.TryGetValue<TestApiFactory>( TestApiFactory? factory = scenario.TryGetValue<TestApiFactory>(
FactoryKey, FactoryKey,
@@ -39,9 +40,7 @@ public class AuthSteps(ScenarioContext scenario)
private static string GetRequiredEnvVar(string name) private static string GetRequiredEnvVar(string name)
{ {
return Environment.GetEnvironmentVariable(name) return Environment.GetEnvironmentVariable(name)
?? throw new InvalidOperationException( ?? throw new InvalidOperationException($"{name} environment variable is not set");
$"{name} environment variable is not set"
);
} }
private static string GenerateJwtToken( private static string GenerateJwtToken(
@@ -57,21 +56,13 @@ public class AuthSteps(ScenarioContext scenario)
private static Guid ParseRegisteredUserId(JsonElement root) private static Guid ParseRegisteredUserId(JsonElement root)
{ {
return root return root.GetProperty("payload").GetProperty("userAccountId").GetGuid();
.GetProperty("payload")
.GetProperty("userAccountId")
.GetGuid();
} }
private static string ParseRegisteredUsername(JsonElement root) private static string ParseRegisteredUsername(JsonElement root)
{ {
return root return root.GetProperty("payload").GetProperty("username").GetString()
.GetProperty("payload") ?? throw new InvalidOperationException("username missing from registration payload");
.GetProperty("username")
.GetString()
?? throw new InvalidOperationException(
"username missing from registration payload"
);
} }
private static string ParseTokenFromPayload( private static string ParseTokenFromPayload(
@@ -85,9 +76,7 @@ public class AuthSteps(ScenarioContext scenario)
|| payload.TryGetProperty(pascalCaseName, out tokenElem) || payload.TryGetProperty(pascalCaseName, out tokenElem)
) )
return tokenElem.GetString() return tokenElem.GetString()
?? throw new InvalidOperationException( ?? throw new InvalidOperationException($"{camelCaseName} is null");
$"{camelCaseName} is null"
);
throw new InvalidOperationException( throw new InvalidOperationException(
$"Could not find token field '{camelCaseName}' in payload" $"Could not find token field '{camelCaseName}' in payload"
@@ -113,22 +102,15 @@ public class AuthSteps(ScenarioContext scenario)
(string username, string password) = scenario.TryGetValue<( (string username, string password) = scenario.TryGetValue<(
string username, string username,
string password string password
)>(TestUserKey, out (string username, string password) user) )>(TestUserKey, out (string username, string password) user)
? user ? user
: ("test.user", "password"); : ("test.user", "password");
string body = JsonSerializer.Serialize(new { username, password }); string body = JsonSerializer.Serialize(new { username, password });
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/login")
HttpMethod.Post,
"/api/auth/login"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -144,16 +126,9 @@ public class AuthSteps(ScenarioContext scenario)
HttpClient client = GetClient(); HttpClient client = GetClient();
string body = JsonSerializer.Serialize(new { password = "test" }); string body = JsonSerializer.Serialize(new { password = "test" });
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/login")
HttpMethod.Post,
"/api/auth/login"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -169,16 +144,9 @@ public class AuthSteps(ScenarioContext scenario)
HttpClient client = GetClient(); HttpClient client = GetClient();
string body = JsonSerializer.Serialize(new { username = "test" }); string body = JsonSerializer.Serialize(new { username = "test" });
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/login")
HttpMethod.Post,
"/api/auth/login"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -192,16 +160,9 @@ public class AuthSteps(ScenarioContext scenario)
public async Task WhenISubmitALoginRequestWithBothUsernameAndPasswordMissing() public async Task WhenISubmitALoginRequestWithBothUsernameAndPasswordMissing()
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/login")
HttpMethod.Post,
"/api/auth/login"
)
{ {
Content = new StringContent( Content = new StringContent("{}", Encoding.UTF8, "application/json"),
"{}",
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -218,10 +179,7 @@ public class AuthSteps(ScenarioContext scenario)
.TryGetValue<HttpResponseMessage>(ResponseKey, out HttpResponseMessage? response) .TryGetValue<HttpResponseMessage>(ResponseKey, out HttpResponseMessage? response)
.Should() .Should()
.BeTrue(); .BeTrue();
scenario scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
.TryGetValue<string>(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
JsonDocument doc = JsonDocument.Parse(responseBody!); JsonDocument doc = JsonDocument.Parse(responseBody!);
JsonElement root = doc.RootElement; JsonElement root = doc.RootElement;
@@ -236,11 +194,7 @@ public class AuthSteps(ScenarioContext scenario)
payloadElem.TryGetProperty("accessToken", out tokenElem) payloadElem.TryGetProperty("accessToken", out tokenElem)
|| payloadElem.TryGetProperty("AccessToken", out tokenElem); || payloadElem.TryGetProperty("AccessToken", out tokenElem);
hasToken hasToken.Should().BeTrue("Expected an access token either at the root or inside 'payload'");
.Should()
.BeTrue(
"Expected an access token either at the root or inside 'payload'"
);
string? token = tokenElem.GetString(); string? token = tokenElem.GetString();
token.Should().NotBeNullOrEmpty(); token.Should().NotBeNullOrEmpty();
@@ -251,16 +205,9 @@ public class AuthSteps(ScenarioContext scenario)
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
// testing GET // testing GET
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/auth/login")
HttpMethod.Get,
"/api/auth/login"
)
{ {
Content = new StringContent( Content = new StringContent("{}", Encoding.UTF8, "application/json"),
"{}",
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -282,7 +229,8 @@ public class AuthSteps(ScenarioContext scenario)
string email = row["Email"] ?? ""; string email = row["Email"] ?? "";
string dateOfBirth = row["DateOfBirth"] ?? ""; string dateOfBirth = row["DateOfBirth"] ?? "";
if (dateOfBirth == "{underage_date}") dateOfBirth = DateTime.UtcNow.AddYears(-18).ToString("yyyy-MM-dd"); if (dateOfBirth == "{underage_date}")
dateOfBirth = DateTime.UtcNow.AddYears(-18).ToString("yyyy-MM-dd");
// Keep default registration fixture values unique across repeated runs. // Keep default registration fixture values unique across repeated runs.
if (email == "newuser@example.com") if (email == "newuser@example.com")
@@ -290,7 +238,8 @@ public class AuthSteps(ScenarioContext scenario)
string suffix = Guid.NewGuid().ToString("N")[..8]; string suffix = Guid.NewGuid().ToString("N")[..8];
email = $"newuser-{suffix}@example.com"; email = $"newuser-{suffix}@example.com";
if (username == "newuser") username = $"newuser-{suffix}"; if (username == "newuser")
username = $"newuser-{suffix}";
} }
string? password = row["Password"]; string? password = row["Password"];
@@ -302,21 +251,14 @@ public class AuthSteps(ScenarioContext scenario)
lastName, lastName,
email, email,
dateOfBirth, dateOfBirth,
password password,
}; };
string body = JsonSerializer.Serialize(registrationData); string body = JsonSerializer.Serialize(registrationData);
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/register")
HttpMethod.Post,
"/api/auth/register"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -330,16 +272,9 @@ public class AuthSteps(ScenarioContext scenario)
public async Task WhenISubmitARegistrationRequestUsingAGetRequest() public async Task WhenISubmitARegistrationRequestUsingAGetRequest()
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/auth/register")
HttpMethod.Get,
"/api/auth/register"
)
{ {
Content = new StringContent( Content = new StringContent("{}", Encoding.UTF8, "application/json"),
"{}",
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -361,20 +296,13 @@ public class AuthSteps(ScenarioContext scenario)
lastName = "User", lastName = "User",
email = $"newuser-{suffix}@example.com", email = $"newuser-{suffix}@example.com",
dateOfBirth = "1990-01-01", dateOfBirth = "1990-01-01",
password = "Password1!" password = "Password1!",
}; };
string body = JsonSerializer.Serialize(registrationData); string body = JsonSerializer.Serialize(registrationData);
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/register")
HttpMethod.Post,
"/api/auth/register"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -395,16 +323,9 @@ public class AuthSteps(ScenarioContext scenario)
var loginData = new { username = "test.user", password = "password" }; var loginData = new { username = "test.user", password = "password" };
string body = JsonSerializer.Serialize(loginData); string body = JsonSerializer.Serialize(loginData);
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/login")
HttpMethod.Post,
"/api/auth/login"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -445,17 +366,10 @@ public class AuthSteps(ScenarioContext scenario)
{ {
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id) Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
? id ? id
: throw new InvalidOperationException( : throw new InvalidOperationException("registered user ID not found in scenario");
"registered user ID not found in scenario" string? username = scenario.TryGetValue<string>(RegisteredUsernameKey, out string? user)
);
string? username = scenario.TryGetValue<string>(
RegisteredUsernameKey,
out string? user
)
? user ? user
: throw new InvalidOperationException( : throw new InvalidOperationException("registered username not found in scenario");
"registered username not found in scenario"
);
string secret = GetRequiredEnvVar("ACCESS_TOKEN_SECRET"); string secret = GetRequiredEnvVar("ACCESS_TOKEN_SECRET");
scenario["accessToken"] = GenerateJwtToken( scenario["accessToken"] = GenerateJwtToken(
@@ -471,17 +385,10 @@ public class AuthSteps(ScenarioContext scenario)
{ {
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id) Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
? id ? id
: throw new InvalidOperationException( : throw new InvalidOperationException("registered user ID not found in scenario");
"registered user ID not found in scenario" string? username = scenario.TryGetValue<string>(RegisteredUsernameKey, out string? user)
);
string? username = scenario.TryGetValue<string>(
RegisteredUsernameKey,
out string? user
)
? user ? user
: throw new InvalidOperationException( : throw new InvalidOperationException("registered username not found in scenario");
"registered username not found in scenario"
);
string secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET"); string secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
scenario["confirmationToken"] = GenerateJwtToken( scenario["confirmationToken"] = GenerateJwtToken(
@@ -497,17 +404,10 @@ public class AuthSteps(ScenarioContext scenario)
{ {
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id) Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
? id ? id
: throw new InvalidOperationException( : throw new InvalidOperationException("registered user ID not found in scenario");
"registered user ID not found in scenario" string? username = scenario.TryGetValue<string>(RegisteredUsernameKey, out string? user)
);
string? username = scenario.TryGetValue<string>(
RegisteredUsernameKey,
out string? user
)
? user ? user
: throw new InvalidOperationException( : throw new InvalidOperationException("registered username not found in scenario");
"registered username not found in scenario"
);
string secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET"); string secret = GetRequiredEnvVar("CONFIRMATION_TOKEN_SECRET");
scenario["confirmationToken"] = GenerateJwtToken( scenario["confirmationToken"] = GenerateJwtToken(
@@ -523,20 +423,12 @@ public class AuthSteps(ScenarioContext scenario)
{ {
Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id) Guid userId = scenario.TryGetValue<Guid>(RegisteredUserIdKey, out Guid id)
? id ? id
: throw new InvalidOperationException( : throw new InvalidOperationException("registered user ID not found in scenario");
"registered user ID not found in scenario" string? username = scenario.TryGetValue<string>(RegisteredUsernameKey, out string? user)
);
string? username = scenario.TryGetValue<string>(
RegisteredUsernameKey,
out string? user
)
? user ? user
: throw new InvalidOperationException( : throw new InvalidOperationException("registered username not found in scenario");
"registered username not found in scenario"
);
const string wrongSecret = const string wrongSecret = "wrong-confirmation-secret-that-is-very-long-1234567890";
"wrong-confirmation-secret-that-is-very-long-1234567890";
scenario["confirmationToken"] = GenerateJwtToken( scenario["confirmationToken"] = GenerateJwtToken(
userId, userId,
username, username,
@@ -545,9 +437,7 @@ public class AuthSteps(ScenarioContext scenario)
); );
} }
[When( [When("I submit a request to a protected endpoint with a valid access token")]
"I submit a request to a protected endpoint with a valid access token"
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithAValidAccessToken() public async Task WhenISubmitARequestToAProtectedEndpointWithAValidAccessToken()
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
@@ -555,12 +445,9 @@ public class AuthSteps(ScenarioContext scenario)
? t ? t
: "invalid-token"; : "invalid-token";
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/protected")
HttpMethod.Get,
"/api/protected"
)
{ {
Headers = { { "Authorization", $"Bearer {token}" } } Headers = { { "Authorization", $"Bearer {token}" } },
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -569,18 +456,13 @@ public class AuthSteps(ScenarioContext scenario)
scenario[ResponseBodyKey] = responseBody; scenario[ResponseBodyKey] = responseBody;
} }
[When( [When("I submit a request to a protected endpoint with an invalid access token")]
"I submit a request to a protected endpoint with an invalid access token"
)]
public async Task WhenISubmitARequestToAProtectedEndpointWithAnInvalidAccessToken() public async Task WhenISubmitARequestToAProtectedEndpointWithAnInvalidAccessToken()
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/protected")
HttpMethod.Get,
"/api/protected"
)
{ {
Headers = { { "Authorization", "Bearer invalid-token-format" } } Headers = { { "Authorization", "Bearer invalid-token-format" } },
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -673,16 +555,9 @@ public class AuthSteps(ScenarioContext scenario)
: "valid-refresh-token"; : "valid-refresh-token";
string body = JsonSerializer.Serialize(new { refreshToken = token }); string body = JsonSerializer.Serialize(new { refreshToken = token });
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/refresh")
HttpMethod.Post,
"/api/auth/refresh"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -695,20 +570,11 @@ public class AuthSteps(ScenarioContext scenario)
public async Task WhenISubmitARefreshTokenRequestWithAnInvalidRefreshToken() public async Task WhenISubmitARefreshTokenRequestWithAnInvalidRefreshToken()
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
string body = JsonSerializer.Serialize( string body = JsonSerializer.Serialize(new { refreshToken = "invalid-refresh-token" });
new { refreshToken = "invalid-refresh-token" }
);
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/refresh")
HttpMethod.Post,
"/api/auth/refresh"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -722,20 +588,11 @@ public class AuthSteps(ScenarioContext scenario)
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
// Use an expired token // Use an expired token
string body = JsonSerializer.Serialize( string body = JsonSerializer.Serialize(new { refreshToken = "expired-refresh-token" });
new { refreshToken = "expired-refresh-token" }
);
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/refresh")
HttpMethod.Post,
"/api/auth/refresh"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -750,16 +607,9 @@ public class AuthSteps(ScenarioContext scenario)
HttpClient client = GetClient(); HttpClient client = GetClient();
string body = JsonSerializer.Serialize(new { }); string body = JsonSerializer.Serialize(new { });
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Post, "/api/auth/refresh")
HttpMethod.Post,
"/api/auth/refresh"
)
{ {
Content = new StringContent( Content = new StringContent(body, Encoding.UTF8, "application/json"),
body,
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -772,16 +622,9 @@ public class AuthSteps(ScenarioContext scenario)
public async Task WhenISubmitARefreshTokenRequestUsingAGETRequest() public async Task WhenISubmitARefreshTokenRequestUsingAGETRequest()
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/auth/refresh")
HttpMethod.Get,
"/api/auth/refresh"
)
{ {
Content = new StringContent( Content = new StringContent("{}", Encoding.UTF8, "application/json"),
"{}",
Encoding.UTF8,
"application/json"
)
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -795,10 +638,7 @@ public class AuthSteps(ScenarioContext scenario)
public async Task WhenISubmitARequestToAProtectedEndpointWithoutAnAccessToken() public async Task WhenISubmitARequestToAProtectedEndpointWithoutAnAccessToken()
{ {
HttpClient client = GetClient(); HttpClient client = GetClient();
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/protected");
HttpMethod.Get,
"/api/protected"
);
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
string responseBody = await response.Content.ReadAsStringAsync(); string responseBody = await response.Content.ReadAsStringAsync();
@@ -830,12 +670,9 @@ public class AuthSteps(ScenarioContext scenario)
? t ? t
: "expired-token"; : "expired-token";
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/protected")
HttpMethod.Get,
"/api/protected"
)
{ {
Headers = { { "Authorization", $"Bearer {token}" } } Headers = { { "Authorization", $"Bearer {token}" } },
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -852,12 +689,9 @@ public class AuthSteps(ScenarioContext scenario)
? t ? t
: "tampered-token"; : "tampered-token";
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/protected")
HttpMethod.Get,
"/api/protected"
)
{ {
Headers = { { "Authorization", $"Bearer {token}" } } Headers = { { "Authorization", $"Bearer {token}" } },
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -876,12 +710,9 @@ public class AuthSteps(ScenarioContext scenario)
? t ? t
: "refresh-token"; : "refresh-token";
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/protected")
HttpMethod.Get,
"/api/protected"
)
{ {
Headers = { { "Authorization", $"Bearer {token}" } } Headers = { { "Authorization", $"Bearer {token}" } },
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -991,12 +822,9 @@ public class AuthSteps(ScenarioContext scenario)
? t ? t
: "confirmation-token"; : "confirmation-token";
HttpRequestMessage requestMessage = new( HttpRequestMessage requestMessage = new(HttpMethod.Get, "/api/protected")
HttpMethod.Get,
"/api/protected"
)
{ {
Headers = { { "Authorization", $"Bearer {token}" } } Headers = { { "Authorization", $"Bearer {token}" } },
}; };
HttpResponseMessage response = await client.SendAsync(requestMessage); HttpResponseMessage response = await client.SendAsync(requestMessage);
@@ -1049,54 +877,30 @@ public class AuthSteps(ScenarioContext scenario)
[Then("the response JSON should have a new access token")] [Then("the response JSON should have a new access token")]
public void ThenTheResponseJsonShouldHaveANewAccessToken() public void ThenTheResponseJsonShouldHaveANewAccessToken()
{ {
scenario scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
.TryGetValue<string>(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
using JsonDocument doc = JsonDocument.Parse(responseBody!); using JsonDocument doc = JsonDocument.Parse(responseBody!);
JsonElement payload = doc.RootElement.GetProperty("payload"); JsonElement payload = doc.RootElement.GetProperty("payload");
string accessToken = ParseTokenFromPayload( string accessToken = ParseTokenFromPayload(payload, "accessToken", "AccessToken");
payload,
"accessToken",
"AccessToken"
);
accessToken.Should().NotBeNullOrWhiteSpace(); accessToken.Should().NotBeNullOrWhiteSpace();
if ( if (scenario.TryGetValue<string>(PreviousAccessTokenKey, out string? previousAccessToken))
scenario.TryGetValue<string>(
PreviousAccessTokenKey,
out string? previousAccessToken
)
)
accessToken.Should().NotBe(previousAccessToken); accessToken.Should().NotBe(previousAccessToken);
} }
[Then("the response JSON should have a new refresh token")] [Then("the response JSON should have a new refresh token")]
public void ThenTheResponseJsonShouldHaveANewRefreshToken() public void ThenTheResponseJsonShouldHaveANewRefreshToken()
{ {
scenario scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
.TryGetValue<string>(ResponseBodyKey, out string? responseBody)
.Should()
.BeTrue();
using JsonDocument doc = JsonDocument.Parse(responseBody!); using JsonDocument doc = JsonDocument.Parse(responseBody!);
JsonElement payload = doc.RootElement.GetProperty("payload"); JsonElement payload = doc.RootElement.GetProperty("payload");
string refreshToken = ParseTokenFromPayload( string refreshToken = ParseTokenFromPayload(payload, "refreshToken", "RefreshToken");
payload,
"refreshToken",
"RefreshToken"
);
refreshToken.Should().NotBeNullOrWhiteSpace(); refreshToken.Should().NotBeNullOrWhiteSpace();
if ( if (scenario.TryGetValue<string>(PreviousRefreshTokenKey, out string? previousRefreshToken))
scenario.TryGetValue<string>(
PreviousRefreshTokenKey,
out string? previousRefreshToken
)
)
refreshToken.Should().NotBe(previousRefreshToken); refreshToken.Should().NotBe(previousRefreshToken);
} }

View File

@@ -20,7 +20,8 @@ public class TestApiFactory : WebApplicationFactory<Program>
d.ServiceType == typeof(IEmailProvider) d.ServiceType == typeof(IEmailProvider)
); );
if (emailProviderDescriptor != null) services.Remove(emailProviderDescriptor); if (emailProviderDescriptor != null)
services.Remove(emailProviderDescriptor);
services.AddScoped<IEmailProvider, MockEmailProvider>(); services.AddScoped<IEmailProvider, MockEmailProvider>();
@@ -29,7 +30,8 @@ public class TestApiFactory : WebApplicationFactory<Program>
d.ServiceType == typeof(IEmailDispatcher) d.ServiceType == typeof(IEmailDispatcher)
); );
if (emailDispatcherDescriptor != null) services.Remove(emailDispatcherDescriptor); if (emailDispatcherDescriptor != null)
services.Remove(emailDispatcherDescriptor);
services.AddScoped<IEmailDispatcher, MockEmailDispatcher>(); services.AddScoped<IEmailDispatcher, MockEmailDispatcher>();
}); });