From f325dd18ee74a8c52a63558e3864fbb5c9da0f72 Mon Sep 17 00:00:00 2001 From: meysamhadeli Date: Fri, 20 May 2022 23:11:12 +0430 Subject: [PATCH] add full integration tests for flight services --- .../EventBus.Messages/FlighContracts.cs | 5 +- src/BuildingBlocks/Domain/Model/Aggregate.cs | 6 +-- .../Domain/Model/{Auditable.cs => Entity.cs} | 3 +- src/BuildingBlocks/Domain/Model/IAggregate.cs | 3 +- src/BuildingBlocks/Domain/Model/IAuditable.cs | 11 ---- src/BuildingBlocks/Domain/Model/IEntity.cs | 11 ++-- src/BuildingBlocks/EFCore/AppDbContextBase.cs | 33 +++++++----- src/BuildingBlocks/EFCore/EfTxBehavior.cs | 4 +- src/BuildingBlocks/EFCore/Extensions.cs | 21 ++++++++ src/BuildingBlocks/Mongo/IMongoRepository.cs | 2 +- src/BuildingBlocks/Mongo/IRepository.cs | 8 +-- src/BuildingBlocks/Mongo/MongoRepository.cs | 2 +- src/Services/Flight/src/Flight/EventMapper.cs | 5 +- .../Events/Domain/FlightDeletedDomainEvent.cs | 9 ++++ .../CreateFlightCommandHandler.cs | 2 +- .../DeleteFlight/DeleteFlightCommand.cs | 6 +++ .../DeleteFlightCommandHandler.cs | 43 ++++++++++++++++ .../DeleteFlightCommandValidator.cs | 14 ++++++ .../DeleteFlight/DeleteFlightEndpoint.cs | 26 ++++++++++ .../UpdateFlightCommandHandler.cs | 2 +- .../src/Flight/Flights/Models/Flight.cs | 23 +++++++++ .../tests/Aircraft/CreateAircraftTests.cs | 34 +++++++++++++ .../tests/Airport/CreateAirportTests.cs | 34 +++++++++++++ .../tests/Fakes/FakeCreateAircraftCommand.cs | 13 +++++ .../tests/Fakes/FakeCreateAirportCommand.cs | 13 +++++ .../tests/Fakes/FakeCreateFlightCommand.cs | 3 +- .../Flight/tests/Flight/CreateFlightTests.cs | 6 +-- .../Flight/tests/Flight/DeleteFlightTests.cs | 50 +++++++++++++++++++ 28 files changed, 336 insertions(+), 56 deletions(-) rename src/BuildingBlocks/Domain/Model/{Auditable.cs => Entity.cs} (73%) delete mode 100644 src/BuildingBlocks/Domain/Model/IAuditable.cs create mode 100644 src/Services/Flight/src/Flight/Flights/Events/Domain/FlightDeletedDomainEvent.cs create mode 100644 src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommand.cs create mode 100644 src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommandHandler.cs create mode 100644 src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommandValidator.cs create mode 100644 src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightEndpoint.cs create mode 100644 src/Services/Flight/tests/Aircraft/CreateAircraftTests.cs create mode 100644 src/Services/Flight/tests/Airport/CreateAirportTests.cs create mode 100644 src/Services/Flight/tests/Fakes/FakeCreateAircraftCommand.cs create mode 100644 src/Services/Flight/tests/Fakes/FakeCreateAirportCommand.cs create mode 100644 src/Services/Flight/tests/Flight/DeleteFlightTests.cs diff --git a/src/BuildingBlocks/Contracts/EventBus.Messages/FlighContracts.cs b/src/BuildingBlocks/Contracts/EventBus.Messages/FlighContracts.cs index 9fab45b..7ba0425 100644 --- a/src/BuildingBlocks/Contracts/EventBus.Messages/FlighContracts.cs +++ b/src/BuildingBlocks/Contracts/EventBus.Messages/FlighContracts.cs @@ -2,7 +2,8 @@ using BuildingBlocks.Domain.Event; namespace BuildingBlocks.Contracts.EventBus.Messages; -public record FlightCreated(string FlightNumber) : IIntegrationEvent; -public record FlightUpdated(string FlightNumber) : IIntegrationEvent; +public record FlightCreated(long Id) : IIntegrationEvent; +public record FlightUpdated(long Id) : IIntegrationEvent; +public record FlightDeleted(long Id) : IIntegrationEvent; public record AircraftCreated(long Id) : IIntegrationEvent; public record AirportCreated(long Id) : IIntegrationEvent; diff --git a/src/BuildingBlocks/Domain/Model/Aggregate.cs b/src/BuildingBlocks/Domain/Model/Aggregate.cs index 36f10d7..eed9ce2 100644 --- a/src/BuildingBlocks/Domain/Model/Aggregate.cs +++ b/src/BuildingBlocks/Domain/Model/Aggregate.cs @@ -6,7 +6,7 @@ namespace BuildingBlocks.Domain.Model { } - public abstract class Aggregate : Auditable, IAggregate + public abstract class Aggregate : Entity, IAggregate { private readonly List _domainEvents = new(); public IReadOnlyList DomainEvents => _domainEvents.AsReadOnly(); @@ -27,8 +27,8 @@ namespace BuildingBlocks.Domain.Model public virtual void When(object @event) { } - public TId Id { get; protected set; } public long Version { get; protected set; } = -1; - public bool IsDeleted { get; protected set; } + + public TId Id { get; protected set; } } } diff --git a/src/BuildingBlocks/Domain/Model/Auditable.cs b/src/BuildingBlocks/Domain/Model/Entity.cs similarity index 73% rename from src/BuildingBlocks/Domain/Model/Auditable.cs rename to src/BuildingBlocks/Domain/Model/Entity.cs index 793ec29..da20d10 100644 --- a/src/BuildingBlocks/Domain/Model/Auditable.cs +++ b/src/BuildingBlocks/Domain/Model/Entity.cs @@ -1,9 +1,10 @@ namespace BuildingBlocks.Domain.Model; -public abstract class Auditable : IAuditable +public abstract class Entity : IEntity { public DateTime? CreatedAt { get; set; } public long? CreatedBy { get; set; } public DateTime? LastModified { get; set; } public long? LastModifiedBy { get; set; } + public bool IsDeleted { get; set; } } diff --git a/src/BuildingBlocks/Domain/Model/IAggregate.cs b/src/BuildingBlocks/Domain/Model/IAggregate.cs index 5c1f03a..f9178ff 100644 --- a/src/BuildingBlocks/Domain/Model/IAggregate.cs +++ b/src/BuildingBlocks/Domain/Model/IAggregate.cs @@ -3,12 +3,11 @@ using BuildingBlocks.EventStoreDB.Events; namespace BuildingBlocks.Domain.Model { - public interface IAggregate : IProjection, IAuditable + public interface IAggregate : IProjection, IEntity { IReadOnlyList DomainEvents { get; } IEvent[] ClearDomainEvents(); long Version { get; } - public bool IsDeleted { get; } } public interface IAggregate : IAggregate diff --git a/src/BuildingBlocks/Domain/Model/IAuditable.cs b/src/BuildingBlocks/Domain/Model/IAuditable.cs deleted file mode 100644 index 348941b..0000000 --- a/src/BuildingBlocks/Domain/Model/IAuditable.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace BuildingBlocks.Domain.Model; - -public interface IAuditable : IEntity -{ - public DateTime? CreatedAt { get; set; } - public long? CreatedBy { get; set; } - public DateTime? LastModified { get; set; } - public long? LastModifiedBy { get; set; } -} - - diff --git a/src/BuildingBlocks/Domain/Model/IEntity.cs b/src/BuildingBlocks/Domain/Model/IEntity.cs index 7cc60fd..c48524c 100644 --- a/src/BuildingBlocks/Domain/Model/IEntity.cs +++ b/src/BuildingBlocks/Domain/Model/IEntity.cs @@ -2,10 +2,9 @@ namespace BuildingBlocks.Domain.Model; public interface IEntity { -} - -public interface IEntity -{ - TId Id { get; } - public bool IsDeleted { get; } + public DateTime? CreatedAt { get; set; } + public long? CreatedBy { get; set; } + public DateTime? LastModified { get; set; } + public long? LastModifiedBy { get; set; } + public bool IsDeleted { get; set; } } diff --git a/src/BuildingBlocks/EFCore/AppDbContextBase.cs b/src/BuildingBlocks/EFCore/AppDbContextBase.cs index 3b5788b..7765222 100644 --- a/src/BuildingBlocks/EFCore/AppDbContextBase.cs +++ b/src/BuildingBlocks/EFCore/AppDbContextBase.cs @@ -21,18 +21,9 @@ public abstract class AppDbContextBase : DbContext, IDbContext _httpContextAccessor = httpContextAccessor; } - protected override void OnModelCreating(ModelBuilder builder) - { - builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); - base.OnModelCreating(builder); - } - public async Task BeginTransactionAsync(CancellationToken cancellationToken = default) { - if (_currentTransaction != null) - { - return; - } + if (_currentTransaction != null) return; _currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); } @@ -92,8 +83,17 @@ public abstract class AppDbContextBase : DbContext, IDbContext return domainEvents.ToImmutableList(); } - // https://www.meziantou.net/entity-framework-core-generate-tracking-columns.htm - // https://www.meziantou.net/entity-framework-core-soft-delete-using-query-filters.htm + protected override void OnModelCreating(ModelBuilder builder) + { + builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + base.OnModelCreating(builder); + + // ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs + builder.FilterSoftDeletedProperties(); + } + + // ref: https://www.meziantou.net/entity-framework-core-generate-tracking-columns.htm + // ref: https://www.meziantou.net/entity-framework-core-soft-delete-using-query-filters.htm private void OnBeforeSaving() { var nameIdentifier = _httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); @@ -102,7 +102,7 @@ public abstract class AppDbContextBase : DbContext, IDbContext foreach (var entry in ChangeTracker.Entries()) { - bool isAuditable = entry.Entity.GetType().IsAssignableTo(typeof(IAggregate)); + var isAuditable = entry.Entity.GetType().IsAssignableTo(typeof(IAggregate)); if (isAuditable) { @@ -117,6 +117,13 @@ public abstract class AppDbContextBase : DbContext, IDbContext entry.Entity.LastModifiedBy = userId; entry.Entity.LastModified = DateTime.Now; break; + + case EntityState.Deleted: + entry.State = EntityState.Modified; + entry.Entity.LastModifiedBy = userId; + entry.Entity.LastModified = DateTime.Now; + entry.Entity.IsDeleted = true; + break; } } } diff --git a/src/BuildingBlocks/EFCore/EfTxBehavior.cs b/src/BuildingBlocks/EFCore/EfTxBehavior.cs index 1220846..9d56f9e 100644 --- a/src/BuildingBlocks/EFCore/EfTxBehavior.cs +++ b/src/BuildingBlocks/EFCore/EfTxBehavior.cs @@ -56,12 +56,12 @@ public class EfTxBehavior : IPipelineBehavior), typeof(TRequest).FullName); + await _dbContextBase.CommitTransactionAsync(cancellationToken); + var domainEvents = _dbContextBase.GetDomainEvents(); await _busPublisher.SendAsync(domainEvents.ToArray(), cancellationToken); - await _dbContextBase.CommitTransactionAsync(cancellationToken); - return response; } catch diff --git a/src/BuildingBlocks/EFCore/Extensions.cs b/src/BuildingBlocks/EFCore/Extensions.cs index 30ace5f..b22fbb6 100644 --- a/src/BuildingBlocks/EFCore/Extensions.cs +++ b/src/BuildingBlocks/EFCore/Extensions.cs @@ -1,4 +1,7 @@ +using System.Linq.Expressions; +using BuildingBlocks.Domain.Model; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -20,4 +23,22 @@ public static class Extensions return services; } + + // ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs + public static void FilterSoftDeletedProperties(this ModelBuilder modelBuilder) + { + Expression> filterExpr = e => !e.IsDeleted; + foreach (var mutableEntityType in modelBuilder.Model.GetEntityTypes() + .Where(m => m.ClrType.IsAssignableTo(typeof(IEntity)))) + { + // modify expression to handle correct child type + var parameter = Expression.Parameter(mutableEntityType.ClrType); + var body = ReplacingExpressionVisitor + .Replace(filterExpr.Parameters.First(), parameter, filterExpr.Body); + var lambdaExpression = Expression.Lambda(body, parameter); + + // set filter + mutableEntityType.SetQueryFilter(lambdaExpression); + } + } } diff --git a/src/BuildingBlocks/Mongo/IMongoRepository.cs b/src/BuildingBlocks/Mongo/IMongoRepository.cs index 4be4cfb..5b52f04 100644 --- a/src/BuildingBlocks/Mongo/IMongoRepository.cs +++ b/src/BuildingBlocks/Mongo/IMongoRepository.cs @@ -3,6 +3,6 @@ using BuildingBlocks.Domain.Model; namespace BuildingBlocks.Mongo; public interface IMongoRepository : IRepository - where TEntity : class, IEntity + where TEntity : class, IAggregate { } diff --git a/src/BuildingBlocks/Mongo/IRepository.cs b/src/BuildingBlocks/Mongo/IRepository.cs index fc0982a..da6e390 100644 --- a/src/BuildingBlocks/Mongo/IRepository.cs +++ b/src/BuildingBlocks/Mongo/IRepository.cs @@ -4,7 +4,7 @@ using BuildingBlocks.Domain.Model; namespace BuildingBlocks.Mongo; public interface IReadRepository - where TEntity : class, IEntity + where TEntity : class, IAggregate { Task FindByIdAsync(TId id, CancellationToken cancellationToken = default); @@ -25,7 +25,7 @@ public interface IReadRepository } public interface IWriteRepository - where TEntity : class, IEntity + where TEntity : class, IAggregate { Task AddAsync(TEntity entity, CancellationToken cancellationToken = default); Task UpdateAsync(TEntity entity, CancellationToken cancellationToken = default); @@ -39,11 +39,11 @@ public interface IRepository : IReadRepository, IWriteRepository, IDisposable - where TEntity : class, IEntity + where TEntity : class, IAggregate { } public interface IRepository : IRepository - where TEntity : class, IEntity + where TEntity : class, IAggregate { } diff --git a/src/BuildingBlocks/Mongo/MongoRepository.cs b/src/BuildingBlocks/Mongo/MongoRepository.cs index 773d7db..e09cbe6 100644 --- a/src/BuildingBlocks/Mongo/MongoRepository.cs +++ b/src/BuildingBlocks/Mongo/MongoRepository.cs @@ -5,7 +5,7 @@ using MongoDB.Driver; namespace BuildingBlocks.Mongo; public class MongoRepository : IMongoRepository - where TEntity : class, IEntity + where TEntity : class, IAggregate { private readonly IMongoDbContext _context; protected readonly IMongoCollection DbSet; diff --git a/src/Services/Flight/src/Flight/EventMapper.cs b/src/Services/Flight/src/Flight/EventMapper.cs index 86c0fdd..a0812ef 100644 --- a/src/Services/Flight/src/Flight/EventMapper.cs +++ b/src/Services/Flight/src/Flight/EventMapper.cs @@ -18,8 +18,9 @@ public sealed class EventMapper : IEventMapper { return @event switch { - FlightCreatedDomainEvent e => new FlightCreated(e.FlightNumber), - FlightUpdatedDomainEvent e => new FlightUpdated(e.FlightNumber), + FlightCreatedDomainEvent e => new FlightCreated(e.Id), + FlightUpdatedDomainEvent e => new FlightUpdated(e.Id), + FlightDeletedDomainEvent e => new FlightDeleted(e.Id), AirportCreatedDomainEvent e => new AirportCreated(e.Id), AircraftCreatedDomainEvent e => new AircraftCreated(e.Id), _ => null diff --git a/src/Services/Flight/src/Flight/Flights/Events/Domain/FlightDeletedDomainEvent.cs b/src/Services/Flight/src/Flight/Flights/Events/Domain/FlightDeletedDomainEvent.cs new file mode 100644 index 0000000..66040a4 --- /dev/null +++ b/src/Services/Flight/src/Flight/Flights/Events/Domain/FlightDeletedDomainEvent.cs @@ -0,0 +1,9 @@ +using System; +using BuildingBlocks.Domain.Event; +using Flight.Flights.Models; + +namespace Flight.Flights.Events.Domain; + +public record FlightDeletedDomainEvent(long Id, string FlightNumber, long AircraftId, DateTime DepartureDate, + long DepartureAirportId, DateTime ArriveDate, long ArriveAirportId, decimal DurationMinutes, + DateTime FlightDate, FlightStatus Status, decimal Price, bool IsDeleted) : IDomainEvent; diff --git a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs index a2dca53..ee42d97 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/CreateFlight/CreateFlightCommandHandler.cs @@ -33,7 +33,7 @@ public class CreateFlightCommandHandler : IRequestHandler; diff --git a/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommandHandler.cs new file mode 100644 index 0000000..2a4c6ab --- /dev/null +++ b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommandHandler.cs @@ -0,0 +1,43 @@ +using System.Threading; +using System.Threading.Tasks; +using Ardalis.GuardClauses; +using Flight.Data; +using Flight.Flights.Dtos; +using Flight.Flights.Exceptions; +using Flight.Flights.Models; +using MapsterMapper; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace Flight.Flights.Features.DeleteFlight; + +public class DeleteFlightCommandHandler : IRequestHandler +{ + private readonly FlightDbContext _flightDbContext; + private readonly IMapper _mapper; + + public DeleteFlightCommandHandler(IMapper mapper, FlightDbContext flightDbContext) + { + _mapper = mapper; + _flightDbContext = flightDbContext; + } + + public async Task Handle(DeleteFlightCommand command, CancellationToken cancellationToken) + { + Guard.Against.Null(command, nameof(command)); + + var flight = await _flightDbContext.Flights.SingleOrDefaultAsync(x => x.Id == command.Id, cancellationToken); + + if (flight is null) + throw new FlightNotFountException(); + + + var deleteFlight = _flightDbContext.Flights.Remove(flight).Entity; + + flight.Delete(deleteFlight.Id, deleteFlight.FlightNumber, deleteFlight.AircraftId, deleteFlight.DepartureAirportId, + deleteFlight.DepartureDate, deleteFlight.ArriveDate, deleteFlight.ArriveAirportId, deleteFlight.DurationMinutes, + deleteFlight.FlightDate, deleteFlight.Status, deleteFlight.Price); + + return _mapper.Map(deleteFlight); + } +} diff --git a/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommandValidator.cs b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommandValidator.cs new file mode 100644 index 0000000..416865f --- /dev/null +++ b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightCommandValidator.cs @@ -0,0 +1,14 @@ +using FluentValidation; + +namespace Flight.Flights.Features.DeleteFlight; + +public class DeleteFlightCommandValidator : AbstractValidator +{ + public DeleteFlightCommandValidator() + { + CascadeMode = CascadeMode.Stop; + + RuleFor(x => x.Id).NotEmpty(); + } +} + diff --git a/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightEndpoint.cs b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightEndpoint.cs new file mode 100644 index 0000000..2045b43 --- /dev/null +++ b/src/Services/Flight/src/Flight/Flights/Features/DeleteFlight/DeleteFlightEndpoint.cs @@ -0,0 +1,26 @@ +using System.Threading; +using System.Threading.Tasks; +using BuildingBlocks.Web; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Swashbuckle.AspNetCore.Annotations; + +namespace Flight.Flights.Features.DeleteFlight; + +[Route(BaseApiPath + "/flight")] +public class DeleteFlightEndpoint : BaseController +{ + [Authorize] + [HttpDelete] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [SwaggerOperation(Summary = "Delete flight", Description = "Delete flight")] + public async Task Update(DeleteFlightCommand command, CancellationToken cancellationToken) + { + var result = await Mediator.Send(command, cancellationToken); + + return Ok(result); + } +} + diff --git a/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/UpdateFlightCommandHandler.cs b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/UpdateFlightCommandHandler.cs index 3758afd..3ba88ea 100644 --- a/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/UpdateFlightCommandHandler.cs +++ b/src/Services/Flight/src/Flight/Flights/Features/UpdateFlight/UpdateFlightCommandHandler.cs @@ -35,7 +35,7 @@ public class UpdateFlightCommandHandler : IRequestHandler AddDomainEvent(@event); } + + public void Delete(long id, string flightNumber, long aircraftId, + long departureAirportId, DateTime departureDate, DateTime arriveDate, + long arriveAirportId, decimal durationMinutes, DateTime flightDate, FlightStatus status, + decimal price, bool isDeleted = true) + { + FlightNumber = flightNumber; + AircraftId = aircraftId; + DepartureAirportId = departureAirportId; + DepartureDate = departureDate; + arriveDate = ArriveDate; + ArriveAirportId = arriveAirportId; + DurationMinutes = durationMinutes; + FlightDate = flightDate; + Status = status; + Price = price; + IsDeleted = isDeleted; + + var @event = new FlightDeletedDomainEvent(id, flightNumber, aircraftId, departureDate, departureAirportId, + arriveDate, arriveAirportId, durationMinutes, flightDate, status, price, isDeleted); + + AddDomainEvent(@event); + } } diff --git a/src/Services/Flight/tests/Aircraft/CreateAircraftTests.cs b/src/Services/Flight/tests/Aircraft/CreateAircraftTests.cs new file mode 100644 index 0000000..060c463 --- /dev/null +++ b/src/Services/Flight/tests/Aircraft/CreateAircraftTests.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using BuildingBlocks.Contracts.EventBus.Messages; +using FluentAssertions; +using Integration.Test.Fakes; +using Xunit; + +namespace Integration.Test.Aircraft; + +[Collection(nameof(TestFixture))] +public class CreateAircraftTests +{ + private readonly TestFixture _fixture; + + public CreateAircraftTests(TestFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task should_create_new_aircraft_to_db_and_publish_message_to_broker() + { + // Arrange + var command = new FakeCreateAircraftCommand().Generate(); + + // Act + var aircraftResponse = await _fixture.SendAsync(command); + + // Assert + aircraftResponse.Should().NotBeNull(); + aircraftResponse?.Name.Should().Be(command.Name); + (await _fixture.IsFaultyPublished()).Should().BeFalse(); + (await _fixture.IsPublished()).Should().BeTrue(); + } +} diff --git a/src/Services/Flight/tests/Airport/CreateAirportTests.cs b/src/Services/Flight/tests/Airport/CreateAirportTests.cs new file mode 100644 index 0000000..2b73679 --- /dev/null +++ b/src/Services/Flight/tests/Airport/CreateAirportTests.cs @@ -0,0 +1,34 @@ +using System.Threading.Tasks; +using BuildingBlocks.Contracts.EventBus.Messages; +using FluentAssertions; +using Integration.Test.Fakes; +using Xunit; + +namespace Integration.Test.Airport; + +[Collection(nameof(TestFixture))] +public class CreateAirportTests +{ + private readonly TestFixture _fixture; + + public CreateAirportTests(TestFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task should_create_new_airport_to_db_and_publish_message_to_broker() + { + // Arrange + var command = new FakeCreateAirportCommand().Generate(); + + // Act + var airportResponse = await _fixture.SendAsync(command); + + // Assert + airportResponse.Should().NotBeNull(); + airportResponse?.Name.Should().Be(command.Name); + (await _fixture.IsFaultyPublished()).Should().BeFalse(); + (await _fixture.IsPublished()).Should().BeTrue(); + } +} diff --git a/src/Services/Flight/tests/Fakes/FakeCreateAircraftCommand.cs b/src/Services/Flight/tests/Fakes/FakeCreateAircraftCommand.cs new file mode 100644 index 0000000..3ae5518 --- /dev/null +++ b/src/Services/Flight/tests/Fakes/FakeCreateAircraftCommand.cs @@ -0,0 +1,13 @@ +using AutoBogus; +using BuildingBlocks.IdsGenerator; +using Flight.Aircrafts.Features.CreateAircraft; + +namespace Integration.Test.Fakes; + +public class FakeCreateAircraftCommand : AutoFaker +{ + public FakeCreateAircraftCommand() + { + RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId()); + } +} diff --git a/src/Services/Flight/tests/Fakes/FakeCreateAirportCommand.cs b/src/Services/Flight/tests/Fakes/FakeCreateAirportCommand.cs new file mode 100644 index 0000000..5e6c479 --- /dev/null +++ b/src/Services/Flight/tests/Fakes/FakeCreateAirportCommand.cs @@ -0,0 +1,13 @@ +using AutoBogus; +using BuildingBlocks.IdsGenerator; +using Flight.Airports.Features.CreateAirport; + +namespace Integration.Test.Fakes; + +public class FakeCreateAirportCommand : AutoFaker +{ + public FakeCreateAirportCommand() + { + RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId()); + } +} diff --git a/src/Services/Flight/tests/Fakes/FakeCreateFlightCommand.cs b/src/Services/Flight/tests/Fakes/FakeCreateFlightCommand.cs index 6951855..d3f2c92 100644 --- a/src/Services/Flight/tests/Fakes/FakeCreateFlightCommand.cs +++ b/src/Services/Flight/tests/Fakes/FakeCreateFlightCommand.cs @@ -1,4 +1,5 @@ using AutoBogus; +using BuildingBlocks.IdsGenerator; using Flight.Flights.Features.CreateFlight; namespace Integration.Test.Fakes; @@ -7,7 +8,7 @@ public sealed class FakeCreateFlightCommand : AutoFaker { public FakeCreateFlightCommand() { - RuleFor(r => r.Id, r => r.Random.Number(50, 100000)); + RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId()); RuleFor(r => r.FlightNumber, r => r.Random.String()); RuleFor(r => r.DepartureAirportId, _ => 1); RuleFor(r => r.ArriveAirportId, _ => 2); diff --git a/src/Services/Flight/tests/Flight/CreateFlightTests.cs b/src/Services/Flight/tests/Flight/CreateFlightTests.cs index 25e4c9c..aa52366 100644 --- a/src/Services/Flight/tests/Flight/CreateFlightTests.cs +++ b/src/Services/Flight/tests/Flight/CreateFlightTests.cs @@ -21,11 +21,7 @@ public class CreateFlightTests public async Task should_create_new_flight_to_db_and_publish_message_to_broker() { // Arrange - var fakeFlight = new FakeCreateFlightCommand().Generate(); - var command = new CreateFlightCommand(fakeFlight.FlightNumber, fakeFlight.AircraftId, - fakeFlight.DepartureAirportId, fakeFlight.DepartureDate, - fakeFlight.ArriveDate, fakeFlight.ArriveAirportId, fakeFlight.DurationMinutes, fakeFlight.FlightDate, - fakeFlight.Status, fakeFlight.Price); + var command = new FakeCreateFlightCommand().Generate(); // Act var flightResponse = await _fixture.SendAsync(command); diff --git a/src/Services/Flight/tests/Flight/DeleteFlightTests.cs b/src/Services/Flight/tests/Flight/DeleteFlightTests.cs new file mode 100644 index 0000000..f43d14f --- /dev/null +++ b/src/Services/Flight/tests/Flight/DeleteFlightTests.cs @@ -0,0 +1,50 @@ +using System.Linq; +using System.Threading.Tasks; +using BuildingBlocks.Contracts.EventBus.Messages; +using Flight.Flights.Features.CreateFlight; +using Flight.Flights.Features.DeleteFlight; +using FluentAssertions; +using Integration.Test.Fakes; +using Microsoft.EntityFrameworkCore; +using Xunit; + +namespace Integration.Test.Flight; + +[Collection(nameof(TestFixture))] +public class DeleteFlightTests +{ + private readonly TestFixture _fixture; + + public DeleteFlightTests(TestFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task should_delete_flight_from_db() + { + // Arrange + var createFlightCommand = new FakeCreateFlightCommand().Generate(); + var flightEntity = global::Flight.Flights.Models.Flight.Create( + createFlightCommand.Id, createFlightCommand.FlightNumber, createFlightCommand.AircraftId, createFlightCommand.DepartureAirportId, + createFlightCommand.DepartureDate, createFlightCommand.ArriveDate, createFlightCommand.ArriveAirportId, createFlightCommand.DurationMinutes, + createFlightCommand.FlightDate, createFlightCommand.Status, createFlightCommand.Price); + await _fixture.InsertAsync(flightEntity); + + var command = new DeleteFlightCommand(flightEntity.Id); + + // Act + await _fixture.SendAsync(command); + var deletedFlight = (await _fixture.ExecuteDbContextAsync(db => db.Flights + .Where(x => x.Id == command.Id) + .IgnoreQueryFilters() + .ToListAsync()) + ).FirstOrDefault(); + + // Assert + deletedFlight?.IsDeleted.Should().BeTrue(); + (await _fixture.IsFaultyPublished()).Should().BeFalse(); + (await _fixture.IsPublished()).Should().BeTrue(); + } +} +