Skip to content

Commit

Permalink
docs: Add a sample on how to use data annotations with SimpleResults (#…
Browse files Browse the repository at this point in the history
…57)

* test: Add integration tests
  • Loading branch information
MrDave1999 authored Dec 18, 2023
1 parent 44d2ffe commit c89101a
Show file tree
Hide file tree
Showing 9 changed files with 208 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace SimpleResults.Example.AspNetCore.Controllers.ManualValidation
{
[Route("Order-ManualValidation")]
[Tags("Order ManualValidation")]
public class OrderController : ControllerBase
{
private readonly OrderService _orderService;

public OrderController(OrderService orderService)
{
_orderService = orderService;
}

[HttpPost]
public Result<CreatedGuid> Create([FromBody]Order order)
{
if (ModelState.IsFailed())
return ModelState.Invalid();

return _orderService.Create(order);
}
}
}

namespace SimpleResults.Example.AspNetCore.Controllers.AutomaticValidation
{
// Allows automatic model validation.
[ApiController]
[Route("Order-AutomaticValidation")]
[Tags("Order AutomaticValidation")]
public class OrderController : ControllerBase
{
private readonly OrderService _orderService;

public OrderController(OrderService orderService)
{
_orderService = orderService;
}

[HttpPost]
public Result<CreatedGuid> Create([FromBody]Order order)
=> _orderService.Create(order);
}
}
3 changes: 1 addition & 2 deletions samples/SimpleResults.Example.AspNetCore/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
global using SimpleResults.Example.AspNetCore;
global using SimpleResults.Example.AspNetCore.MinimalApi;
global using SimpleResults.Example.AspNetCore.Models;
global using SimpleResults.Example.DataAnnotations;
global using SimpleResults.Example.FluentValidation;
global using Person = SimpleResults.Example.FluentValidation.Person;


10 changes: 8 additions & 2 deletions samples/SimpleResults.Example.AspNetCore/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@
builder.Services
.AddSingleton(DataSeeds.CreateUsers())
.AddSingleton<List<Person>>()
.AddSingleton<List<Order>>()
.AddSingleton<UserService>()
.AddSingleton<PersonService>();
.AddSingleton<PersonService>()
.AddSingleton<OrderService>();

builder.Services.AddControllers(options =>
{
// Add filter for all controllers.
options.Filters.Add<TranslateResultToActionResultAttribute>();
})
.ConfigureApiBehaviorOptions(options =>
{
options.InvalidModelStateResponseFactory = (context) => context.ModelState.BadRequest();
});
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
Expand All @@ -26,7 +32,7 @@
}

// Allows to load the default resource in English.
app.UseRequestLocalization("en-US");
app.UseRequestLocalization("en");

app.UseAuthorization();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
namespace SimpleResults.Example.Web.Tests.Features;

public class CreateOrderTests
{
[TestCase(Routes.Order.ManualValidation)]
[TestCase(Routes.Order.AutomaticValidation)]
public async Task Post_WhenOrderIsCreated_ShouldReturnsHttpStatusCodeCreated(string requestUri)
{
// Arrange
using var factory = new WebApplicationFactory<Program>();
var client = factory.CreateClient();
var orders = factory.Services.GetService<List<Order>>();
var order = new Order
{
Description = "Description",
DeliveryAddress = "DeliveryAddress",
Details = new List<OrderDetail>
{
new()
{
Product = "Product.",
Amount = 2,
Price = 5600
}
}
};

// Act
var httpResponse = await client.PostAsJsonAsync(requestUri, order);
var result = await httpResponse
.Content
.ReadFromJsonAsync<Result<CreatedGuid>>();
var expectedId = orders[0].Id;

// Asserts
httpResponse.StatusCode.Should().Be(HttpStatusCode.Created);
result.Data.Id.Should().Be(expectedId);
result.IsSuccess.Should().BeTrue();
result.Message.Should().NotBeNullOrEmpty();
result.Errors.Should().BeEmpty();
}

[TestCase(Routes.Order.ManualValidation)]
[TestCase(Routes.Order.AutomaticValidation)]
public async Task Post_WhenModelIsInvalid_ShouldReturnsHttpStatusCodeBadRequest(string requestUri)
{
// Arrange
using var factory = new WebApplicationFactory<Program>();
var client = factory.CreateClient();
var order = new Order
{
Description = string.Empty,
DeliveryAddress = string.Empty,
Details = new List<OrderDetail>
{
new()
{
Product = string.Empty,
Amount = default,
Price = default
},
new()
{
Product = string.Empty,
Amount = default,
Price = default
}
}
};
var expectedErrors = new[]
{
"'Description' property failed validation. Error was: The Description field is required.",
"'Description' property failed validation. Error was: The field Description must be a string or array type with a minimum length of '10'.",
"'DeliveryAddress' property failed validation. Error was: The DeliveryAddress field is required.",
"'DeliveryAddress' property failed validation. Error was: The field DeliveryAddress must be a string or array type with a minimum length of '10'.",
"'Details[0].Price' property failed validation. Error was: The Price field is required.",
"'Details[0].Amount' property failed validation. Error was: The Amount field is required.",
"'Details[0].Product' property failed validation. Error was: The Product field is required.",
"'Details[0].Product' property failed validation. Error was: The field Product must be a string or array type with a minimum length of '8'.",
"'Details[1].Price' property failed validation. Error was: The Price field is required.",
"'Details[1].Amount' property failed validation. Error was: The Amount field is required.",
"'Details[1].Product' property failed validation. Error was: The Product field is required.",
"'Details[1].Product' property failed validation. Error was: The field Product must be a string or array type with a minimum length of '8'."
};

// Act
var httpResponse = await client.PostAsJsonAsync(requestUri, order);
var result = await httpResponse
.Content
.ReadFromJsonAsync<Result<CreatedGuid>>();

// Asserts
httpResponse.StatusCode.Should().Be(HttpStatusCode.BadRequest);
result.Data.Should().BeNull();
result.IsSuccess.Should().BeFalse();
result.Message.Should().NotBeNullOrEmpty();
result.Errors.Should().BeEquivalentTo(expectedErrors);
}
}
3 changes: 2 additions & 1 deletion samples/SimpleResults.Example.Web.Tests/GlobalUsings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
global using System.Net;

global using SimpleResults.Example.AspNetCore.Models;
global using Person = SimpleResults.Example.FluentValidation.Person;
global using Person = SimpleResults.Example.FluentValidation.Person;
global using SimpleResults.Example.DataAnnotations;
6 changes: 6 additions & 0 deletions samples/SimpleResults.Example.Web.Tests/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ public class Message
public const string WebApi = "/Message-WebApi";
public const string MinimalApi = "/Message-MinimalApi";
}

public class Order
{
public const string ManualValidation = "/Order-ManualValidation";
public const string AutomaticValidation = "/Order-AutomaticValidation";
}
}
25 changes: 25 additions & 0 deletions samples/SimpleResults.Example/DataAnnotations/Order.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;

namespace SimpleResults.Example.DataAnnotations;

public class Order
{
[JsonIgnore]
public string Id { get; set; }
[Required, MinLength(10)]
public string Description { get; set; }
[Required, MinLength(10)]
public string DeliveryAddress { get; set; }
[Required]
public List<OrderDetail> Details { get; set; }
}

public class OrderDetail
{
[Required, MinLength(8)]
public string Product { get; set; }
[Required]
public int? Amount { get; set; }
[Required]
public double? Price { get; set; }
}
21 changes: 21 additions & 0 deletions samples/SimpleResults.Example/DataAnnotations/OrderService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace SimpleResults.Example.DataAnnotations;

public class OrderService
{
private readonly List<Order> _orders;

public OrderService(List<Order> orders)
{
ArgumentNullException.ThrowIfNull(nameof(orders));
_orders = orders;
}

public Result<CreatedGuid> Create(Order order)
{
ArgumentNullException.ThrowIfNull(nameof(order));
_orders.Add(order);
var guid = Guid.NewGuid();
order.Id = guid.ToString();
return Result.CreatedResource(guid);
}
}
3 changes: 2 additions & 1 deletion samples/SimpleResults.Example/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
global using FluentValidation;
global using FluentValidation.Results;
global using FluentValidation.Results;
global using System.Text.Json.Serialization;

0 comments on commit c89101a

Please sign in to comment.