Add consumption logic and endpoints
This commit is contained in:
@@ -9,7 +9,7 @@ START TRANSACTION;
|
|||||||
|
|
||||||
DO $EF$
|
DO $EF$
|
||||||
BEGIN
|
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" (
|
CREATE TABLE "Users" (
|
||||||
"Id" text NOT NULL,
|
"Id" text NOT NULL,
|
||||||
CONSTRAINT "PK_Users" PRIMARY KEY ("Id")
|
CONSTRAINT "PK_Users" PRIMARY KEY ("Id")
|
||||||
@@ -19,7 +19,7 @@ END $EF$;
|
|||||||
|
|
||||||
DO $EF$
|
DO $EF$
|
||||||
BEGIN
|
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" (
|
CREATE TABLE "Cars" (
|
||||||
"Id" uuid NOT NULL,
|
"Id" uuid NOT NULL,
|
||||||
"Name" character varying(50) NOT NULL,
|
"Name" character varying(50) NOT NULL,
|
||||||
@@ -32,39 +32,39 @@ END $EF$;
|
|||||||
|
|
||||||
DO $EF$
|
DO $EF$
|
||||||
BEGIN
|
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 "Consumption" (
|
CREATE TABLE "Consumptions" (
|
||||||
"Id" uuid NOT NULL,
|
"Id" uuid NOT NULL,
|
||||||
"DateTime" timestamp with time zone NOT NULL,
|
"DateTime" timestamp with time zone NOT NULL,
|
||||||
"Distance" double precision NOT NULL,
|
"Distance" double precision NOT NULL,
|
||||||
"Amount" double precision NOT NULL,
|
"Amount" double precision NOT NULL,
|
||||||
"IgnoreInCalculation" boolean NOT NULL,
|
"IgnoreInCalculation" boolean NOT NULL,
|
||||||
"CarId" uuid NOT NULL,
|
"CarId" uuid NOT NULL,
|
||||||
CONSTRAINT "PK_Consumption" PRIMARY KEY ("Id"),
|
CONSTRAINT "PK_Consumptions" PRIMARY KEY ("Id"),
|
||||||
CONSTRAINT "FK_Consumption_Cars_CarId" FOREIGN KEY ("CarId") REFERENCES "Cars" ("Id") ON DELETE CASCADE
|
CONSTRAINT "FK_Consumptions_Cars_CarId" FOREIGN KEY ("CarId") REFERENCES "Cars" ("Id") ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
END IF;
|
END IF;
|
||||||
END $EF$;
|
END $EF$;
|
||||||
|
|
||||||
DO $EF$
|
DO $EF$
|
||||||
BEGIN
|
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");
|
CREATE INDEX "IX_Cars_UserId" ON "Cars" ("UserId");
|
||||||
END IF;
|
END IF;
|
||||||
END $EF$;
|
END $EF$;
|
||||||
|
|
||||||
DO $EF$
|
DO $EF$
|
||||||
BEGIN
|
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_Consumption_CarId" ON "Consumption" ("CarId");
|
CREATE INDEX "IX_Consumptions_CarId" ON "Consumptions" ("CarId");
|
||||||
END IF;
|
END IF;
|
||||||
END $EF$;
|
END $EF$;
|
||||||
|
|
||||||
DO $EF$
|
DO $EF$
|
||||||
BEGIN
|
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")
|
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||||
VALUES ('20240817153531_Initial', '8.0.8');
|
VALUES ('20240818105918_Initial', '8.0.8');
|
||||||
END IF;
|
END IF;
|
||||||
END $EF$;
|
END $EF$;
|
||||||
COMMIT;
|
COMMIT;
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ public static class GetCar
|
|||||||
.WithTags("Cars");
|
.WithTags("Cars");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<IResult> Endpoint(
|
private static async Task<IResult> Endpoint(
|
||||||
Guid id,
|
Guid id,
|
||||||
ApplicationDbContext dbContext,
|
ApplicationDbContext dbContext,
|
||||||
CancellationToken cancellationToken)
|
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)
|
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 Microsoft.Extensions.DependencyInjection.Extensions;
|
||||||
using Vegasco.WebApi.Cars;
|
using Vegasco.WebApi.Cars;
|
||||||
using Vegasco.WebApi.Common;
|
using Vegasco.WebApi.Common;
|
||||||
|
using Vegasco.WebApi.Consumptions;
|
||||||
|
|
||||||
namespace Vegasco.WebApi.Endpoints;
|
namespace Vegasco.WebApi.Endpoints;
|
||||||
|
|
||||||
@@ -39,5 +40,11 @@ public static class EndpointExtensions
|
|||||||
CreateCar.MapEndpoint(versionedApis);
|
CreateCar.MapEndpoint(versionedApis);
|
||||||
UpdateCar.MapEndpoint(versionedApis);
|
UpdateCar.MapEndpoint(versionedApis);
|
||||||
DeleteCar.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 Microsoft.EntityFrameworkCore;
|
||||||
using Vegasco.WebApi.Cars;
|
using Vegasco.WebApi.Cars;
|
||||||
using Vegasco.WebApi.Common;
|
using Vegasco.WebApi.Common;
|
||||||
|
using Vegasco.WebApi.Consumptions;
|
||||||
using Vegasco.WebApi.Users;
|
using Vegasco.WebApi.Users;
|
||||||
|
|
||||||
namespace Vegasco.WebApi.Persistence;
|
namespace Vegasco.WebApi.Persistence;
|
||||||
@@ -11,6 +12,8 @@ public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> options
|
|||||||
|
|
||||||
public DbSet<User> Users { get; set; }
|
public DbSet<User> Users { get; set; }
|
||||||
|
|
||||||
|
public DbSet<Consumption> Consumptions { get; set; }
|
||||||
|
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
base.OnModelCreating(modelBuilder);
|
base.OnModelCreating(modelBuilder);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using Vegasco.WebApi.Persistence;
|
|||||||
namespace Vegasco.WebApi.Persistence.Migrations
|
namespace Vegasco.WebApi.Persistence.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApplicationDbContext))]
|
[DbContext(typeof(ApplicationDbContext))]
|
||||||
[Migration("20240817153531_Initial")]
|
[Migration("20240818105918_Initial")]
|
||||||
partial class Initial
|
partial class Initial
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -70,7 +70,7 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
|||||||
|
|
||||||
b.HasIndex("CarId");
|
b.HasIndex("CarId");
|
||||||
|
|
||||||
b.ToTable("Consumption");
|
b.ToTable("Consumptions");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Vegasco.WebApi.Users.User", b =>
|
modelBuilder.Entity("Vegasco.WebApi.Users.User", b =>
|
||||||
@@ -42,7 +42,7 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
|||||||
});
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "Consumption",
|
name: "Consumptions",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
{
|
{
|
||||||
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
Id = table.Column<Guid>(type: "uuid", nullable: false),
|
||||||
@@ -54,9 +54,9 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
|||||||
},
|
},
|
||||||
constraints: table =>
|
constraints: table =>
|
||||||
{
|
{
|
||||||
table.PrimaryKey("PK_Consumption", x => x.Id);
|
table.PrimaryKey("PK_Consumptions", x => x.Id);
|
||||||
table.ForeignKey(
|
table.ForeignKey(
|
||||||
name: "FK_Consumption_Cars_CarId",
|
name: "FK_Consumptions_Cars_CarId",
|
||||||
column: x => x.CarId,
|
column: x => x.CarId,
|
||||||
principalTable: "Cars",
|
principalTable: "Cars",
|
||||||
principalColumn: "Id",
|
principalColumn: "Id",
|
||||||
@@ -69,8 +69,8 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
|||||||
column: "UserId");
|
column: "UserId");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Consumption_CarId",
|
name: "IX_Consumptions_CarId",
|
||||||
table: "Consumption",
|
table: "Consumptions",
|
||||||
column: "CarId");
|
column: "CarId");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
|||||||
protected override void Down(MigrationBuilder migrationBuilder)
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
{
|
{
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Consumption");
|
name: "Consumptions");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "Cars");
|
name: "Cars");
|
||||||
@@ -67,7 +67,7 @@ namespace Vegasco.WebApi.Persistence.Migrations
|
|||||||
|
|
||||||
b.HasIndex("CarId");
|
b.HasIndex("CarId");
|
||||||
|
|
||||||
b.ToTable("Consumption");
|
b.ToTable("Consumptions");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Vegasco.WebApi.Users.User", b =>
|
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