diff --git a/src/FiscalOS.API/Accounts/Connect/Endpoint.cs b/src/FiscalOS.API/Accounts/Connect/Endpoint.cs index 2ec2ba5..1085541 100644 --- a/src/FiscalOS.API/Accounts/Connect/Endpoint.cs +++ b/src/FiscalOS.API/Accounts/Connect/Endpoint.cs @@ -9,7 +9,9 @@ internal static class Endpoint return groupBuilder.MapPost(Route, HandleAsync); } - private static async Task HandleAsync() + private static async Task HandleAsync( + [FromBody] Request request + ) { return Results.Ok(); } diff --git a/src/FiscalOS.API/Accounts/Connect/Request.cs b/src/FiscalOS.API/Accounts/Connect/Request.cs new file mode 100644 index 0000000..980369a --- /dev/null +++ b/src/FiscalOS.API/Accounts/Connect/Request.cs @@ -0,0 +1,22 @@ +namespace FiscalOS.API.Accounts.Connect; + +public record Request : IValidatableObject +{ + public string PublicToken { get; init; } = string.Empty; + public string PlaidInstitutionId { get; init; } = string.Empty; + + public IEnumerable Validate(ValidationContext validationContext) + { + if (string.IsNullOrWhiteSpace(PublicToken)) + { + var fieldName = nameof(PublicToken); + yield return new($"The {fieldName} field is required.", [fieldName]); + } + + if (string.IsNullOrWhiteSpace(PlaidInstitutionId)) + { + var fieldName = nameof(PlaidInstitutionId); + yield return new($"The {fieldName} field is required.", [fieldName]); + } + } +} \ No newline at end of file diff --git a/tests/FiscalOS.API.Tests/Integration/ConnectTests.cs b/tests/FiscalOS.API.Tests/Integration/ConnectTests.cs index e017cfd..57b2888 100644 --- a/tests/FiscalOS.API.Tests/Integration/ConnectTests.cs +++ b/tests/FiscalOS.API.Tests/Integration/ConnectTests.cs @@ -11,4 +11,73 @@ public class ConnectTests(TestApi testApi) : IntegrationTest(testApi) await res.Should().BeProblemDetails(HttpStatusCode.Unauthorized); } + + [Fact] + public async Task Connect_WhenCalledWithoutPublicTokenOrPlaidInstitutionId_ItShouldReturn400WithProblemDetails() + { + var jwt = JwtTokenBuilder.New() + .WithClaim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()) + .Build(); + + using var content = new StringContent(JsonSerializer.Serialize(new { }), Encoding.UTF8, "application/json"); + using var request = new HttpRequestMessage(HttpMethod.Post, ConnectUri) + { + Content = content, + }; + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt); + + var response = await Client.SendAsync(request, TestContext.Current.CancellationToken); + + await response.Should().BeValidationProblemDetails(new Dictionary() + { + ["PublicToken"] = ["The PublicToken field is required."], + ["PlaidInstitutionId"] = ["The PlaidInstitutionId field is required."], + }); + } + + [Fact] + public async Task Connect_WhenCalledWithoutPublicToken_ItShouldReturn400WithProblemDetails() + { + var jwt = JwtTokenBuilder.New() + .WithClaim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()) + .Build(); + + var json = JsonSerializer.Serialize(new { plaidInstitutionId = "id" }); + using var content = new StringContent(json, Encoding.UTF8, "application/json"); + using var request = new HttpRequestMessage(HttpMethod.Post, ConnectUri) + { + Content = content, + }; + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt); + + var response = await Client.SendAsync(request, TestContext.Current.CancellationToken); + + await response.Should().BeValidationProblemDetails(new Dictionary() + { + ["PublicToken"] = ["The PublicToken field is required."], + }); + } + + [Fact] + public async Task Connect_WhenCalledWithoutPlaidInstitutionId_ItShouldReturn400WithProblemDetails() + { + var jwt = JwtTokenBuilder.New() + .WithClaim(JwtRegisteredClaimNames.Sub, Guid.NewGuid().ToString()) + .Build(); + + var json = JsonSerializer.Serialize(new { publicToken = "token" }); + using var content = new StringContent(json, Encoding.UTF8, "application/json"); + using var request = new HttpRequestMessage(HttpMethod.Post, ConnectUri) + { + Content = content, + }; + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", jwt); + + var response = await Client.SendAsync(request, TestContext.Current.CancellationToken); + + await response.Should().BeValidationProblemDetails(new Dictionary() + { + ["PlaidInstitutionId"] = ["The PlaidInstitutionId field is required."], + }); + } } \ No newline at end of file diff --git a/tests/FiscalOS.API.Tests/Usings.cs b/tests/FiscalOS.API.Tests/Usings.cs index d403688..e9f8ff4 100644 --- a/tests/FiscalOS.API.Tests/Usings.cs +++ b/tests/FiscalOS.API.Tests/Usings.cs @@ -4,6 +4,8 @@ global using System.Net.Http.Headers; global using System.Net.Http.Json; global using System.Security.Claims; global using System.Security.Cryptography; +global using System.Text; +global using System.Text.Json; global using AwesomeAssertions.Execution; global using AwesomeAssertions.Primitives;