Add consumption logic and endpoints
This commit is contained in:
@@ -9,7 +9,7 @@ START TRANSACTION;
|
||||
|
||||
DO $EF$
|
||||
BEGIN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN
|
||||
CREATE TABLE "Users" (
|
||||
"Id" text NOT NULL,
|
||||
CONSTRAINT "PK_Users" PRIMARY KEY ("Id")
|
||||
@@ -19,7 +19,7 @@ END $EF$;
|
||||
|
||||
DO $EF$
|
||||
BEGIN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN
|
||||
CREATE TABLE "Cars" (
|
||||
"Id" uuid NOT NULL,
|
||||
"Name" character varying(50) NOT NULL,
|
||||
@@ -32,39 +32,39 @@ END $EF$;
|
||||
|
||||
DO $EF$
|
||||
BEGIN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN
|
||||
CREATE TABLE "Consumption" (
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN
|
||||
CREATE TABLE "Consumptions" (
|
||||
"Id" uuid NOT NULL,
|
||||
"DateTime" timestamp with time zone NOT NULL,
|
||||
"Distance" double precision NOT NULL,
|
||||
"Amount" double precision NOT NULL,
|
||||
"IgnoreInCalculation" boolean NOT NULL,
|
||||
"CarId" uuid NOT NULL,
|
||||
CONSTRAINT "PK_Consumption" PRIMARY KEY ("Id"),
|
||||
CONSTRAINT "FK_Consumption_Cars_CarId" FOREIGN KEY ("CarId") REFERENCES "Cars" ("Id") ON DELETE CASCADE
|
||||
CONSTRAINT "PK_Consumptions" PRIMARY KEY ("Id"),
|
||||
CONSTRAINT "FK_Consumptions_Cars_CarId" FOREIGN KEY ("CarId") REFERENCES "Cars" ("Id") ON DELETE CASCADE
|
||||
);
|
||||
END IF;
|
||||
END $EF$;
|
||||
|
||||
DO $EF$
|
||||
BEGIN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN
|
||||
CREATE INDEX "IX_Cars_UserId" ON "Cars" ("UserId");
|
||||
END IF;
|
||||
END $EF$;
|
||||
|
||||
DO $EF$
|
||||
BEGIN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN
|
||||
CREATE INDEX "IX_Consumption_CarId" ON "Consumption" ("CarId");
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN
|
||||
CREATE INDEX "IX_Consumptions_CarId" ON "Consumptions" ("CarId");
|
||||
END IF;
|
||||
END $EF$;
|
||||
|
||||
DO $EF$
|
||||
BEGIN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240817153531_Initial') THEN
|
||||
IF NOT EXISTS(SELECT 1 FROM "__EFMigrationsHistory" WHERE "MigrationId" = '20240818105918_Initial') THEN
|
||||
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||
VALUES ('20240817153531_Initial', '8.0.8');
|
||||
VALUES ('20240818105918_Initial', '8.0.8');
|
||||
END IF;
|
||||
END $EF$;
|
||||
COMMIT;
|
||||
|
||||
@@ -13,12 +13,12 @@ public static class GetCar
|
||||
.WithTags("Cars");
|
||||
}
|
||||
|
||||
public static async Task<IResult> Endpoint(
|
||||
private static async Task<IResult> Endpoint(
|
||||
Guid id,
|
||||
ApplicationDbContext dbContext,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken);
|
||||
Car? car = await dbContext.Cars.FindAsync([new CarId(id)], cancellationToken: cancellationToken);
|
||||
|
||||
if (car is null)
|
||||
{
|
||||
|
||||
74
src/WebApi/Consumptions/CreateConsumption.cs
Normal file
74
src/WebApi/Consumptions/CreateConsumption.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Vegasco.WebApi.Cars;
|
||||
using Vegasco.WebApi.Common;
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace Vegasco.WebApi.Consumptions;
|
||||
|
||||
public static class CreateConsumption
|
||||
{
|
||||
public record Request(DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId);
|
||||
|
||||
public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId);
|
||||
|
||||
public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
return builder
|
||||
.MapPost("consumptions", Endpoint)
|
||||
.WithTags("Consumptions");
|
||||
}
|
||||
|
||||
public class Validator : AbstractValidator<Request>
|
||||
{
|
||||
public Validator(TimeProvider timeProvider)
|
||||
{
|
||||
RuleFor(x => x.DateTime.ToUniversalTime())
|
||||
.LessThanOrEqualTo(timeProvider.GetUtcNow())
|
||||
.WithName(nameof(Request.DateTime));
|
||||
|
||||
RuleFor(x => x.Distance)
|
||||
.GreaterThan(0);
|
||||
|
||||
RuleFor(x => x.Amount)
|
||||
.GreaterThan(0);
|
||||
|
||||
RuleFor(x => x.CarId)
|
||||
.NotEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> Endpoint(
|
||||
ApplicationDbContext dbContext,
|
||||
Request request,
|
||||
IEnumerable<IValidator<Request>> validators,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
List<ValidationResult> failedValidations = await validators.ValidateAllAsync(request, cancellationToken);
|
||||
if (failedValidations.Count > 0)
|
||||
{
|
||||
return TypedResults.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary()));
|
||||
}
|
||||
|
||||
Car? car = await dbContext.Cars.FindAsync([new CarId(request.CarId)], cancellationToken);
|
||||
if (car is null)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
var consumption = new Consumption
|
||||
{
|
||||
DateTime = request.DateTime.ToUniversalTime(),
|
||||
Distance = request.Distance,
|
||||
Amount = request.Amount,
|
||||
IgnoreInCalculation = request.IgnoreInCalculation,
|
||||
CarId = new CarId(request.CarId)
|
||||
};
|
||||
|
||||
dbContext.Consumptions.Add(consumption);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return TypedResults.Created($"consumptions/{consumption.Id.Value}",
|
||||
new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, consumption.IgnoreInCalculation, consumption.CarId.Value));
|
||||
}
|
||||
}
|
||||
30
src/WebApi/Consumptions/DeleteConsumptions.cs
Normal file
30
src/WebApi/Consumptions/DeleteConsumptions.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace Vegasco.WebApi.Consumptions;
|
||||
|
||||
public static class DeleteConsumption
|
||||
{
|
||||
public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
return builder
|
||||
.MapDelete("consumptions/{id:guid}", Endpoint)
|
||||
.WithTags("Consumptions");
|
||||
}
|
||||
|
||||
private static async Task<IResult> Endpoint(
|
||||
ApplicationDbContext dbContext,
|
||||
Guid id,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Consumption? consumption = await dbContext.Consumptions.FindAsync([new ConsumptionId(id)], cancellationToken);
|
||||
if (consumption is null)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
dbContext.Consumptions.Remove(consumption);
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return TypedResults.NoContent();
|
||||
}
|
||||
}
|
||||
32
src/WebApi/Consumptions/GetConsumption.cs
Normal file
32
src/WebApi/Consumptions/GetConsumption.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace Vegasco.WebApi.Consumptions;
|
||||
|
||||
public static class GetConsumption
|
||||
{
|
||||
public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId);
|
||||
|
||||
public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
return builder
|
||||
.MapGet("consumptions/{id:guid}", Endpoint)
|
||||
.WithTags("Consumptions");
|
||||
}
|
||||
|
||||
private static async Task<IResult> Endpoint(
|
||||
ApplicationDbContext dbContext,
|
||||
Guid id,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Consumption? consumption = await dbContext.Consumptions.FindAsync([new ConsumptionId(id)], cancellationToken);
|
||||
|
||||
if (consumption is null)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
var response = new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance,
|
||||
consumption.Amount, consumption.IgnoreInCalculation, consumption.CarId.Value);
|
||||
return TypedResults.Ok(response);
|
||||
}
|
||||
}
|
||||
27
src/WebApi/Consumptions/GetConsumptions.cs
Normal file
27
src/WebApi/Consumptions/GetConsumptions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace Vegasco.WebApi.Consumptions;
|
||||
|
||||
public static class GetConsumptions
|
||||
{
|
||||
public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId);
|
||||
|
||||
public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
return builder
|
||||
.MapGet("consumptions", Endpoint)
|
||||
.WithTags("Consumptions");
|
||||
}
|
||||
|
||||
private static async Task<IResult> Endpoint(
|
||||
ApplicationDbContext dbContext,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var consumptions = await dbContext.Consumptions
|
||||
.Select(x => new Response(x.Id.Value, x.DateTime, x.Distance, x.Amount, x.IgnoreInCalculation, x.CarId.Value))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return TypedResults.Ok(consumptions);
|
||||
}
|
||||
}
|
||||
65
src/WebApi/Consumptions/UpdateConsumption.cs
Normal file
65
src/WebApi/Consumptions/UpdateConsumption.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using FluentValidation;
|
||||
using FluentValidation.Results;
|
||||
using Vegasco.WebApi.Common;
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace Vegasco.WebApi.Consumptions;
|
||||
|
||||
public static class UpdateConsumption
|
||||
{
|
||||
public record Request(DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation);
|
||||
|
||||
public record Response(Guid Id, DateTimeOffset DateTime, double Distance, double Amount, bool IgnoreInCalculation, Guid CarId);
|
||||
|
||||
public static RouteHandlerBuilder MapEndpoint(IEndpointRouteBuilder builder)
|
||||
{
|
||||
return builder
|
||||
.MapPut("consumptions/{id:guid}", Endpoint)
|
||||
.WithTags("Consumptions");
|
||||
}
|
||||
|
||||
public class Validator : AbstractValidator<Request>
|
||||
{
|
||||
public Validator(TimeProvider timeProvider)
|
||||
{
|
||||
RuleFor(x => x.DateTime.ToUniversalTime())
|
||||
.LessThanOrEqualTo(timeProvider.GetUtcNow())
|
||||
.WithName(nameof(Request.DateTime));
|
||||
|
||||
RuleFor(x => x.Distance)
|
||||
.GreaterThan(0);
|
||||
|
||||
RuleFor(x => x.Amount)
|
||||
.GreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<IResult> Endpoint(
|
||||
ApplicationDbContext dbContext,
|
||||
Guid id,
|
||||
Request request,
|
||||
IEnumerable<IValidator<Request>> validators,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
List<ValidationResult> failedValidations = await validators.ValidateAllAsync(request, cancellationToken);
|
||||
if (failedValidations.Count > 0)
|
||||
{
|
||||
return TypedResults.BadRequest(new HttpValidationProblemDetails(failedValidations.ToCombinedDictionary()));
|
||||
}
|
||||
|
||||
Consumption? consumption = await dbContext.Consumptions.FindAsync([new ConsumptionId(id)], cancellationToken);
|
||||
if (consumption is null)
|
||||
{
|
||||
return TypedResults.NotFound();
|
||||
}
|
||||
|
||||
consumption.DateTime = request.DateTime.ToUniversalTime();
|
||||
consumption.Distance = request.Distance;
|
||||
consumption.Amount = request.Amount;
|
||||
consumption.IgnoreInCalculation = request.IgnoreInCalculation;
|
||||
|
||||
await dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
return TypedResults.Ok(new Response(consumption.Id.Value, consumption.DateTime, consumption.Distance, consumption.Amount, consumption.IgnoreInCalculation, consumption.CarId.Value));
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using Asp.Versioning.Conventions;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Vegasco.WebApi.Cars;
|
||||
using Vegasco.WebApi.Common;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
|
||||
namespace Vegasco.WebApi.Endpoints;
|
||||
|
||||
@@ -39,5 +40,11 @@ public static class EndpointExtensions
|
||||
CreateCar.MapEndpoint(versionedApis);
|
||||
UpdateCar.MapEndpoint(versionedApis);
|
||||
DeleteCar.MapEndpoint(versionedApis);
|
||||
|
||||
GetConsumptions.MapEndpoint(versionedApis);
|
||||
GetConsumption.MapEndpoint(versionedApis);
|
||||
CreateConsumption.MapEndpoint(versionedApis);
|
||||
UpdateConsumption.MapEndpoint(versionedApis);
|
||||
DeleteConsumption.MapEndpoint(versionedApis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Vegasco.WebApi.Cars;
|
||||
using Vegasco.WebApi.Common;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
using Vegasco.WebApi.Users;
|
||||
|
||||
namespace Vegasco.WebApi.Persistence;
|
||||
@@ -11,6 +12,8 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
|
||||
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
public DbSet<Consumption> Consumptions { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
@@ -12,7 +12,7 @@ using Vegasco.WebApi.Persistence;
|
||||
namespace Vegasco.WebApi.Persistence.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20240817153531_Initial")]
|
||||
[Migration("20240818105918_Initial")]
|
||||
partial class Initial
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@@ -70,7 +70,7 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
||||
|
||||
b.HasIndex("CarId");
|
||||
|
||||
b.ToTable("Consumption");
|
||||
b.ToTable("Consumptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vegasco.WebApi.Users.User", b =>
|
||||
@@ -42,7 +42,7 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Consumption",
|
||||
name: "Consumptions",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||
@@ -54,9 +54,9 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Consumption", x => x.Id);
|
||||
table.PrimaryKey("PK_Consumptions", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Consumption_Cars_CarId",
|
||||
name: "FK_Consumptions_Cars_CarId",
|
||||
column: x => x.CarId,
|
||||
principalTable: "Cars",
|
||||
principalColumn: "Id",
|
||||
@@ -69,8 +69,8 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Consumption_CarId",
|
||||
table: "Consumption",
|
||||
name: "IX_Consumptions_CarId",
|
||||
table: "Consumptions",
|
||||
column: "CarId");
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "Consumption");
|
||||
name: "Consumptions");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Cars");
|
||||
@@ -67,7 +67,7 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
||||
|
||||
b.HasIndex("CarId");
|
||||
|
||||
b.ToTable("Consumption");
|
||||
b.ToTable("Consumptions");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Vegasco.WebApi.Users.User", b =>
|
||||
|
||||
29
tests/WebApi.Tests.Integration/ConsumptionFaker.cs
Normal file
29
tests/WebApi.Tests.Integration/ConsumptionFaker.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Bogus;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
|
||||
namespace WebApi.Tests.Integration;
|
||||
|
||||
internal class ConsumptionFaker
|
||||
{
|
||||
private readonly Faker _faker = new();
|
||||
|
||||
internal CreateConsumption.Request CreateConsumptionRequest(Guid carId)
|
||||
{
|
||||
return new CreateConsumption.Request(
|
||||
_faker.Date.RecentOffset(),
|
||||
_faker.Random.Int(1, 1_000),
|
||||
_faker.Random.Int(20, 70),
|
||||
_faker.Random.Bool(),
|
||||
carId);
|
||||
}
|
||||
|
||||
internal UpdateConsumption.Request UpdateConsumptionRequest()
|
||||
{
|
||||
CreateConsumption.Request createRequest = CreateConsumptionRequest(default);
|
||||
return new UpdateConsumption.Request(
|
||||
createRequest.DateTime,
|
||||
createRequest.Distance,
|
||||
createRequest.Amount,
|
||||
createRequest.IgnoreInCalculation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using Vegasco.WebApi.Cars;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace WebApi.Tests.Integration.Consumptions;
|
||||
|
||||
[Collection(SharedTestCollection.Name)]
|
||||
public class CreateConsumptionTests : IAsyncLifetime
|
||||
{
|
||||
private readonly WebAppFactory _factory;
|
||||
private readonly IServiceScope _scope;
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
|
||||
private readonly CarFaker _carFaker = new();
|
||||
private readonly ConsumptionFaker _consumptionFaker = new();
|
||||
|
||||
public CreateConsumptionTests(WebAppFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_scope = _factory.Services.CreateScope();
|
||||
_dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateConsumption_ShouldCreateConsumption_WhenRequestIsValid()
|
||||
{
|
||||
// Arrange
|
||||
CreateCar.Response createdCarResponse = await CreateCarAsync();
|
||||
|
||||
CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id);
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.Created);
|
||||
var createdConsumption = await response.Content.ReadFromJsonAsync<CreateConsumption.Response>();
|
||||
createdConsumption.Should().BeEquivalentTo(createConsumptionRequest, o => o.ExcludingMissingMembers());
|
||||
|
||||
_dbContext.Consumptions.Should().HaveCount(1)
|
||||
.And.ContainEquivalentOf(createdConsumption, o =>
|
||||
o.ExcludingMissingMembers()
|
||||
.Excluding(x => x!.Id)
|
||||
.Excluding(x => x!.CarId));
|
||||
|
||||
Consumption singleConsumption = _dbContext.Consumptions.Single();
|
||||
singleConsumption.Id.Value.Should().Be(createdConsumption!.Id);
|
||||
singleConsumption.CarId.Value.Should().Be(createdConsumption.CarId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CreateConsumption_ShouldReturnValidationProblems_WhenRequestIsInvalid()
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(Guid.Empty);
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest);
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||
var validationProblemDetails = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>();
|
||||
validationProblemDetails!.Errors.Keys.Should().Contain(x =>
|
||||
x.Equals(nameof(createConsumptionRequest.CarId), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
_dbContext.Consumptions.Should().NotContainEquivalentOf(createConsumptionRequest, o => o.ExcludingMissingMembers());
|
||||
}
|
||||
|
||||
private async Task<CreateCar.Response> CreateCarAsync()
|
||||
{
|
||||
CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest();
|
||||
using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||
createCarResponse.EnsureSuccessStatusCode();
|
||||
var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||
return createdCarResponse!;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
FluentAssertionConfiguration.SetupGlobalConfig();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
_scope.Dispose();
|
||||
await _dbContext.DisposeAsync();
|
||||
await _factory.ResetDatabaseAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using Vegasco.WebApi.Cars;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace WebApi.Tests.Integration.Consumptions;
|
||||
|
||||
[Collection(SharedTestCollection.Name)]
|
||||
public class DeleteConsumptionTests : IAsyncLifetime
|
||||
{
|
||||
private readonly WebAppFactory _factory;
|
||||
private readonly IServiceScope _scope;
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
|
||||
private readonly CarFaker _carFaker = new();
|
||||
private readonly ConsumptionFaker _consumptionFaker = new();
|
||||
|
||||
public DeleteConsumptionTests(WebAppFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_scope = _factory.Services.CreateScope();
|
||||
_dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteConsumption_ShouldDeleteConsumption_WhenConsumptionExists()
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Response createdConsumption = await CreateConsumptionAsync();
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.DeleteAsync($"v1/consumptions/{createdConsumption.Id}");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NoContent);
|
||||
_dbContext.Consumptions.Should().NotContain(x => x.Id.Value == createdConsumption.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteConsumption_ShouldReturnNotFound_WhenConsumptionDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var consumptionId = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.DeleteAsync($"v1/consumptions/{consumptionId}");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
private async Task<CreateConsumption.Response> CreateConsumptionAsync()
|
||||
{
|
||||
CreateCar.Response createdCarResponse = await CreateCarAsync();
|
||||
CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id);
|
||||
using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var createdConsumption = await response.Content.ReadFromJsonAsync<CreateConsumption.Response>();
|
||||
return createdConsumption!;
|
||||
}
|
||||
|
||||
private async Task<CreateCar.Response> CreateCarAsync()
|
||||
{
|
||||
CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest();
|
||||
using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||
createCarResponse.EnsureSuccessStatusCode();
|
||||
var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||
return createdCarResponse!;
|
||||
}
|
||||
|
||||
public Task InitializeAsync() => Task.CompletedTask;
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
_scope.Dispose();
|
||||
await _dbContext.DisposeAsync();
|
||||
await _factory.ResetDatabaseAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using Vegasco.WebApi.Cars;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace WebApi.Tests.Integration.Consumptions;
|
||||
|
||||
[Collection(SharedTestCollection.Name)]
|
||||
public class GetConsumptionTests : IAsyncLifetime
|
||||
{
|
||||
private readonly WebAppFactory _factory;
|
||||
private readonly IServiceScope _scope;
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
|
||||
private readonly CarFaker _carFaker = new();
|
||||
private readonly ConsumptionFaker _consumptionFaker = new();
|
||||
|
||||
public GetConsumptionTests(WebAppFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_scope = _factory.Services.CreateScope();
|
||||
_dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetConsumption_ShouldReturnConsumption_WhenConsumptionExist()
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Response createdConsumption = await CreateConsumptionAsync();
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/consumptions/{createdConsumption.Id}");
|
||||
|
||||
// Assert
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
var consumption = await response.Content.ReadFromJsonAsync<GetConsumption.Response>();
|
||||
consumption.Should().BeEquivalentTo(createdConsumption);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetConsumptions_ShouldReturnNotFound_WhenConsumptionDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
var consumptionId = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.GetAsync($"v1/consumptions{consumptionId}");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
}
|
||||
|
||||
private async Task<CreateConsumption.Response> CreateConsumptionAsync()
|
||||
{
|
||||
CreateCar.Response createdCarResponse = await CreateCarAsync();
|
||||
CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id);
|
||||
using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var createdConsumption = await response.Content.ReadFromJsonAsync<CreateConsumption.Response>();
|
||||
return createdConsumption!;
|
||||
}
|
||||
|
||||
private async Task<CreateCar.Response> CreateCarAsync()
|
||||
{
|
||||
CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest();
|
||||
using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||
createCarResponse.EnsureSuccessStatusCode();
|
||||
var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||
return createdCarResponse!;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
FluentAssertionConfiguration.SetupGlobalConfig();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
_scope.Dispose();
|
||||
await _dbContext.DisposeAsync();
|
||||
await _factory.ResetDatabaseAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using Vegasco.WebApi.Cars;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace WebApi.Tests.Integration.Consumptions;
|
||||
|
||||
[Collection(SharedTestCollection.Name)]
|
||||
public class GetConsumptionsTests : IAsyncLifetime
|
||||
{
|
||||
private readonly WebAppFactory _factory;
|
||||
private readonly IServiceScope _scope;
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
|
||||
private readonly CarFaker _carFaker = new();
|
||||
private readonly ConsumptionFaker _consumptionFaker = new();
|
||||
|
||||
public GetConsumptionsTests(WebAppFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_scope = _factory.Services.CreateScope();
|
||||
_dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetConsumptions_ShouldReturnConsumptions_WhenConsumptionsExist()
|
||||
{
|
||||
// Arrange
|
||||
List<CreateConsumption.Response> createdConsumptions = [];
|
||||
const int numberOfConsumptions = 3;
|
||||
for (var i = 0; i < numberOfConsumptions; i++)
|
||||
{
|
||||
CreateConsumption.Response createdConsumption = await CreateConsumptionAsync();
|
||||
createdConsumptions.Add(createdConsumption);
|
||||
}
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.GetAsync("v1/consumptions");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
var consumptions = await response.Content.ReadFromJsonAsync<List<GetConsumptions.Response>>();
|
||||
consumptions.Should().BeEquivalentTo(createdConsumptions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetConsumptions_ShouldReturnEmptyList_WhenNoConsumptionsExist()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.GetAsync("v1/consumptions");
|
||||
|
||||
// Assert
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
var consumptions = await response.Content.ReadFromJsonAsync<List<GetConsumptions.Response>>();
|
||||
consumptions.Should().BeEmpty();
|
||||
}
|
||||
|
||||
private async Task<CreateConsumption.Response> CreateConsumptionAsync()
|
||||
{
|
||||
CreateCar.Response createdCarResponse = await CreateCarAsync();
|
||||
CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id);
|
||||
using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var createdConsumption = await response.Content.ReadFromJsonAsync<CreateConsumption.Response>();
|
||||
return createdConsumption!;
|
||||
}
|
||||
|
||||
private async Task<CreateCar.Response> CreateCarAsync()
|
||||
{
|
||||
CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest();
|
||||
using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||
createCarResponse.EnsureSuccessStatusCode();
|
||||
var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||
return createdCarResponse!;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
FluentAssertionConfiguration.SetupGlobalConfig();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
_scope.Dispose();
|
||||
await _dbContext.DisposeAsync();
|
||||
await _factory.ResetDatabaseAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
using FluentAssertions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Net;
|
||||
using System.Net.Http.Json;
|
||||
using Vegasco.WebApi.Cars;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
using Vegasco.WebApi.Persistence;
|
||||
|
||||
namespace WebApi.Tests.Integration.Consumptions;
|
||||
|
||||
[Collection(SharedTestCollection.Name)]
|
||||
public class UpdateConsumptionTests : IAsyncLifetime
|
||||
{
|
||||
private readonly WebAppFactory _factory;
|
||||
private readonly IServiceScope _scope;
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
|
||||
private readonly CarFaker _carFaker = new();
|
||||
private readonly ConsumptionFaker _consumptionFaker = new();
|
||||
|
||||
public UpdateConsumptionTests(WebAppFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_scope = _factory.Services.CreateScope();
|
||||
_dbContext = _scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateConsumption_ShouldCreateConsumption_WhenRequestIsValid()
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Response createdConsumption = await CreateConsumptionAsync();
|
||||
UpdateConsumption.Request updateConsumptionRequest = _consumptionFaker.UpdateConsumptionRequest();
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{createdConsumption.Id}", updateConsumptionRequest);
|
||||
|
||||
// Assert
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
response.StatusCode.Should().Be(HttpStatusCode.OK);
|
||||
var updatedConsumption = await response.Content.ReadFromJsonAsync<UpdateConsumption.Response>();
|
||||
updatedConsumption.Should().BeEquivalentTo(updateConsumptionRequest, o => o.ExcludingMissingMembers());
|
||||
|
||||
_dbContext.Consumptions.Should().HaveCount(1)
|
||||
.And.ContainEquivalentOf(updatedConsumption, o =>
|
||||
o.ExcludingMissingMembers()
|
||||
.Excluding(x => x!.Id)
|
||||
.Excluding(x => x!.CarId));
|
||||
|
||||
Consumption singleConsumption = _dbContext.Consumptions.Single();
|
||||
singleConsumption.Id.Value.Should().Be(updatedConsumption!.Id);
|
||||
singleConsumption.CarId.Value.Should().Be(updatedConsumption.CarId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateConsumption_ShouldReturnValidationProblems_WhenRequestIsInvalid()
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Response createdConsumption = await CreateConsumptionAsync();
|
||||
UpdateConsumption.Request updateConsumptionRequest = _consumptionFaker.UpdateConsumptionRequest() with { Distance = -42 };
|
||||
var randomGuid = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{randomGuid}", updateConsumptionRequest);
|
||||
|
||||
// Assert
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
|
||||
var validationProblemDetails = await response.Content.ReadFromJsonAsync<ValidationProblemDetails>();
|
||||
validationProblemDetails!.Errors.Keys.Should().Contain(x =>
|
||||
x.Equals(nameof(updateConsumptionRequest.Distance), StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
_dbContext.Consumptions.Should().NotContainEquivalentOf(updateConsumptionRequest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateConsumption_ShouldReturnNotFound_WhenConsumptionDoesNotExist()
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Response createdConsumption = await CreateConsumptionAsync();
|
||||
UpdateConsumption.Request updateConsumptionRequest = _consumptionFaker.UpdateConsumptionRequest();
|
||||
var randomGuid = Guid.NewGuid();
|
||||
|
||||
// Act
|
||||
using HttpResponseMessage response = await _factory.HttpClient.PutAsJsonAsync($"v1/consumptions/{randomGuid}", updateConsumptionRequest);
|
||||
|
||||
// Assert
|
||||
var content = await response.Content.ReadAsStringAsync();
|
||||
response.StatusCode.Should().Be(HttpStatusCode.NotFound);
|
||||
|
||||
_dbContext.Consumptions.Should().NotContainEquivalentOf(updateConsumptionRequest);
|
||||
}
|
||||
|
||||
private async Task<CreateConsumption.Response> CreateConsumptionAsync()
|
||||
{
|
||||
CreateCar.Response createdCarResponse = await CreateCarAsync();
|
||||
CreateConsumption.Request createConsumptionRequest = _consumptionFaker.CreateConsumptionRequest(createdCarResponse.Id);
|
||||
using HttpResponseMessage response = await _factory.HttpClient.PostAsJsonAsync("v1/consumptions", createConsumptionRequest);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var createdConsumption = await response.Content.ReadFromJsonAsync<CreateConsumption.Response>();
|
||||
return createdConsumption!;
|
||||
}
|
||||
|
||||
private async Task<CreateCar.Response> CreateCarAsync()
|
||||
{
|
||||
CreateCar.Request createCarRequest = new CarFaker().CreateCarRequest();
|
||||
using HttpResponseMessage createCarResponse = await _factory.HttpClient.PostAsJsonAsync("v1/cars", createCarRequest);
|
||||
createCarResponse.EnsureSuccessStatusCode();
|
||||
var createdCarResponse = await createCarResponse.Content.ReadFromJsonAsync<CreateCar.Response>();
|
||||
return createdCarResponse!;
|
||||
}
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
FluentAssertionConfiguration.SetupGlobalConfig();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task DisposeAsync()
|
||||
{
|
||||
_scope.Dispose();
|
||||
await _dbContext.DisposeAsync();
|
||||
await _factory.ResetDatabaseAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentAssertions;
|
||||
|
||||
namespace WebApi.Tests.Integration;
|
||||
internal static class FluentAssertionConfiguration
|
||||
{
|
||||
private const int DateTimeComparisonPrecision = 100;
|
||||
|
||||
internal static void SetupGlobalConfig()
|
||||
{
|
||||
AssertionOptions.AssertEquivalencyUsing(options => options
|
||||
.Using<DateTime>(ctx => ctx.Subject.ToUniversalTime().Should().BeCloseTo(ctx.Expectation.ToUniversalTime(), TimeSpan.FromMilliseconds(DateTimeComparisonPrecision)))
|
||||
.WhenTypeIs<DateTime>());
|
||||
|
||||
AssertionOptions.AssertEquivalencyUsing(options => options
|
||||
.Using<DateTimeOffset>(ctx => ctx.Subject.ToUniversalTime().Should().BeCloseTo(ctx.Expectation.ToUniversalTime(), TimeSpan.FromMilliseconds(DateTimeComparisonPrecision)))
|
||||
.WhenTypeIs<DateTimeOffset>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using FluentAssertions;
|
||||
using FluentValidation.Results;
|
||||
using NSubstitute;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
|
||||
namespace WebApi.Tests.Unit.Consumptions;
|
||||
public class CreateConsumptionRequestValidatorTests
|
||||
{
|
||||
private readonly CreateConsumption.Validator _sut;
|
||||
private readonly TimeProvider _timeProvider = Substitute.For<TimeProvider>();
|
||||
|
||||
private readonly DateTimeOffset _utcNow = new DateTimeOffset(2024, 8, 18, 13, 2, 53, TimeSpan.Zero);
|
||||
|
||||
private readonly CreateConsumption.Request _validRequest;
|
||||
|
||||
public CreateConsumptionRequestValidatorTests()
|
||||
{
|
||||
_timeProvider.GetUtcNow().Returns(_utcNow);
|
||||
_sut = new CreateConsumption.Validator(_timeProvider);
|
||||
|
||||
_validRequest = new CreateConsumption.Request(
|
||||
_utcNow.AddDays(-1),
|
||||
1,
|
||||
1,
|
||||
false,
|
||||
Guid.NewGuid());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_ShouldBeValid_WhenRequestIsValid()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
ValidationResult? result = await _sut.ValidateAsync(_validRequest);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_ShouldBeInvalid_WhenDateTimeIsGreaterThanUtcNow()
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Request request = _validRequest with { DateTime = _utcNow.AddDays(1) };
|
||||
|
||||
// Act
|
||||
ValidationResult? result = await _sut.ValidateAsync(request);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(CreateConsumption.Request.DateTime));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
public async Task ValidateAsync_ShouldBeInvalid_WhenDistanceIsLessThanOrEqualToZero(double distance)
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Request request = _validRequest with { Distance = distance };
|
||||
|
||||
// Act
|
||||
ValidationResult? result = await _sut.ValidateAsync(request);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(CreateConsumption.Request.Distance));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
public async Task ValidateAsync_ShouldBeInvalid_WhenAmountIsLessThanOrEqualToZero(double amount)
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Request request = _validRequest with { Amount = amount };
|
||||
|
||||
// Act
|
||||
ValidationResult? result = await _sut.ValidateAsync(request);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(CreateConsumption.Request.Amount));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_ShouldBeInvalid_WhenCarIdIsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
CreateConsumption.Request request = _validRequest with { CarId = Guid.Empty };
|
||||
|
||||
// Act
|
||||
ValidationResult? result = await _sut.ValidateAsync(request);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(CreateConsumption.Request.CarId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using FluentAssertions;
|
||||
using FluentValidation.Results;
|
||||
using NSubstitute;
|
||||
using Vegasco.WebApi.Consumptions;
|
||||
|
||||
namespace WebApi.Tests.Unit.Consumptions;
|
||||
|
||||
public class UpdateConsumptionRequestValidatorTests
|
||||
{
|
||||
private readonly UpdateConsumption.Validator _sut;
|
||||
private readonly TimeProvider _timeProvider = Substitute.For<TimeProvider>();
|
||||
|
||||
private readonly DateTimeOffset _utcNow = new DateTimeOffset(2024, 8, 18, 13, 2, 53, TimeSpan.Zero);
|
||||
|
||||
private readonly UpdateConsumption.Request _validRequest;
|
||||
|
||||
public UpdateConsumptionRequestValidatorTests()
|
||||
{
|
||||
_timeProvider.GetUtcNow().Returns(_utcNow);
|
||||
_sut = new UpdateConsumption.Validator(_timeProvider);
|
||||
|
||||
_validRequest = new UpdateConsumption.Request(
|
||||
_utcNow.AddDays(-1),
|
||||
1,
|
||||
1,
|
||||
false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_ShouldBeValid_WhenRequestIsValid()
|
||||
{
|
||||
// Arrange
|
||||
|
||||
// Act
|
||||
ValidationResult? result = await _sut.ValidateAsync(_validRequest);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_ShouldBeInvalid_WhenDateTimeIsGreaterThanUtcNow()
|
||||
{
|
||||
// Arrange
|
||||
UpdateConsumption.Request request = _validRequest with { DateTime = _utcNow.AddDays(1) };
|
||||
|
||||
// Act
|
||||
ValidationResult? result = await _sut.ValidateAsync(request);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(UpdateConsumption.Request.DateTime));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
public async Task ValidateAsync_ShouldBeInvalid_WhenDistanceIsLessThanOrEqualToZero(double distance)
|
||||
{
|
||||
// Arrange
|
||||
UpdateConsumption.Request request = _validRequest with { Distance = distance };
|
||||
|
||||
// Act
|
||||
ValidationResult? result = await _sut.ValidateAsync(request);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(UpdateConsumption.Request.Distance));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-1)]
|
||||
public async Task ValidateAsync_ShouldBeInvalid_WhenAmountIsLessThanOrEqualToZero(double amount)
|
||||
{
|
||||
// Arrange
|
||||
UpdateConsumption.Request request = _validRequest with { Amount = amount };
|
||||
|
||||
// Act
|
||||
ValidationResult? result = await _sut.ValidateAsync(request);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().ContainSingle(x => x.PropertyName == nameof(UpdateConsumption.Request.Amount));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user