add full integration tests for flight services

This commit is contained in:
meysamhadeli 2022-05-20 23:11:12 +04:30
parent c7c9b2dd9e
commit f325dd18ee
28 changed files with 336 additions and 56 deletions

View File

@ -2,7 +2,8 @@ using BuildingBlocks.Domain.Event;
namespace BuildingBlocks.Contracts.EventBus.Messages; namespace BuildingBlocks.Contracts.EventBus.Messages;
public record FlightCreated(string FlightNumber) : IIntegrationEvent; public record FlightCreated(long Id) : IIntegrationEvent;
public record FlightUpdated(string FlightNumber) : IIntegrationEvent; public record FlightUpdated(long Id) : IIntegrationEvent;
public record FlightDeleted(long Id) : IIntegrationEvent;
public record AircraftCreated(long Id) : IIntegrationEvent; public record AircraftCreated(long Id) : IIntegrationEvent;
public record AirportCreated(long Id) : IIntegrationEvent; public record AirportCreated(long Id) : IIntegrationEvent;

View File

@ -6,7 +6,7 @@ namespace BuildingBlocks.Domain.Model
{ {
} }
public abstract class Aggregate<TId> : Auditable, IAggregate<TId> public abstract class Aggregate<TId> : Entity, IAggregate<TId>
{ {
private readonly List<IDomainEvent> _domainEvents = new(); private readonly List<IDomainEvent> _domainEvents = new();
public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly(); public IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents.AsReadOnly();
@ -27,8 +27,8 @@ namespace BuildingBlocks.Domain.Model
public virtual void When(object @event) { } public virtual void When(object @event) { }
public TId Id { get; protected set; }
public long Version { get; protected set; } = -1; public long Version { get; protected set; } = -1;
public bool IsDeleted { get; protected set; }
public TId Id { get; protected set; }
} }
} }

View File

@ -1,9 +1,10 @@
namespace BuildingBlocks.Domain.Model; namespace BuildingBlocks.Domain.Model;
public abstract class Auditable : IAuditable public abstract class Entity : IEntity
{ {
public DateTime? CreatedAt { get; set; } public DateTime? CreatedAt { get; set; }
public long? CreatedBy { get; set; } public long? CreatedBy { get; set; }
public DateTime? LastModified { get; set; } public DateTime? LastModified { get; set; }
public long? LastModifiedBy { get; set; } public long? LastModifiedBy { get; set; }
public bool IsDeleted { get; set; }
} }

View File

@ -3,12 +3,11 @@ using BuildingBlocks.EventStoreDB.Events;
namespace BuildingBlocks.Domain.Model namespace BuildingBlocks.Domain.Model
{ {
public interface IAggregate : IProjection, IAuditable public interface IAggregate : IProjection, IEntity
{ {
IReadOnlyList<IDomainEvent> DomainEvents { get; } IReadOnlyList<IDomainEvent> DomainEvents { get; }
IEvent[] ClearDomainEvents(); IEvent[] ClearDomainEvents();
long Version { get; } long Version { get; }
public bool IsDeleted { get; }
} }
public interface IAggregate<out T> : IAggregate public interface IAggregate<out T> : IAggregate

View File

@ -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; }
}

View File

@ -2,10 +2,9 @@ namespace BuildingBlocks.Domain.Model;
public interface IEntity public interface IEntity
{ {
} public DateTime? CreatedAt { get; set; }
public long? CreatedBy { get; set; }
public interface IEntity<out TId> public DateTime? LastModified { get; set; }
{ public long? LastModifiedBy { get; set; }
TId Id { get; } public bool IsDeleted { get; set; }
public bool IsDeleted { get; }
} }

View File

@ -21,18 +21,9 @@ public abstract class AppDbContextBase : DbContext, IDbContext
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
} }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
base.OnModelCreating(builder);
}
public async Task BeginTransactionAsync(CancellationToken cancellationToken = default) public async Task BeginTransactionAsync(CancellationToken cancellationToken = default)
{ {
if (_currentTransaction != null) if (_currentTransaction != null) return;
{
return;
}
_currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken); _currentTransaction = await Database.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken);
} }
@ -92,8 +83,17 @@ public abstract class AppDbContextBase : DbContext, IDbContext
return domainEvents.ToImmutableList(); return domainEvents.ToImmutableList();
} }
// https://www.meziantou.net/entity-framework-core-generate-tracking-columns.htm protected override void OnModelCreating(ModelBuilder builder)
// https://www.meziantou.net/entity-framework-core-soft-delete-using-query-filters.htm {
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() private void OnBeforeSaving()
{ {
var nameIdentifier = _httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier); var nameIdentifier = _httpContextAccessor?.HttpContext?.User?.FindFirstValue(ClaimTypes.NameIdentifier);
@ -102,7 +102,7 @@ public abstract class AppDbContextBase : DbContext, IDbContext
foreach (var entry in ChangeTracker.Entries<IAggregate>()) foreach (var entry in ChangeTracker.Entries<IAggregate>())
{ {
bool isAuditable = entry.Entity.GetType().IsAssignableTo(typeof(IAggregate)); var isAuditable = entry.Entity.GetType().IsAssignableTo(typeof(IAggregate));
if (isAuditable) if (isAuditable)
{ {
@ -117,6 +117,13 @@ public abstract class AppDbContextBase : DbContext, IDbContext
entry.Entity.LastModifiedBy = userId; entry.Entity.LastModifiedBy = userId;
entry.Entity.LastModified = DateTime.Now; entry.Entity.LastModified = DateTime.Now;
break; break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.Entity.LastModifiedBy = userId;
entry.Entity.LastModified = DateTime.Now;
entry.Entity.IsDeleted = true;
break;
} }
} }
} }

View File

@ -56,12 +56,12 @@ public class EfTxBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TRe
nameof(EfTxBehavior<TRequest, TResponse>), nameof(EfTxBehavior<TRequest, TResponse>),
typeof(TRequest).FullName); typeof(TRequest).FullName);
await _dbContextBase.CommitTransactionAsync(cancellationToken);
var domainEvents = _dbContextBase.GetDomainEvents(); var domainEvents = _dbContextBase.GetDomainEvents();
await _busPublisher.SendAsync(domainEvents.ToArray(), cancellationToken); await _busPublisher.SendAsync(domainEvents.ToArray(), cancellationToken);
await _dbContextBase.CommitTransactionAsync(cancellationToken);
return response; return response;
} }
catch catch

View File

@ -1,4 +1,7 @@
using System.Linq.Expressions;
using BuildingBlocks.Domain.Model;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -20,4 +23,22 @@ public static class Extensions
return services; return services;
} }
// ref: https://github.com/pdevito3/MessageBusTestingInMemHarness/blob/main/RecipeManagement/src/RecipeManagement/Databases/RecipesDbContext.cs
public static void FilterSoftDeletedProperties(this ModelBuilder modelBuilder)
{
Expression<Func<IAggregate, bool>> 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);
}
}
} }

View File

@ -3,6 +3,6 @@ using BuildingBlocks.Domain.Model;
namespace BuildingBlocks.Mongo; namespace BuildingBlocks.Mongo;
public interface IMongoRepository<TEntity, in TId> : IRepository<TEntity, TId> public interface IMongoRepository<TEntity, in TId> : IRepository<TEntity, TId>
where TEntity : class, IEntity<TId> where TEntity : class, IAggregate<TId>
{ {
} }

View File

@ -4,7 +4,7 @@ using BuildingBlocks.Domain.Model;
namespace BuildingBlocks.Mongo; namespace BuildingBlocks.Mongo;
public interface IReadRepository<TEntity, in TId> public interface IReadRepository<TEntity, in TId>
where TEntity : class, IEntity<TId> where TEntity : class, IAggregate<TId>
{ {
Task<TEntity?> FindByIdAsync(TId id, CancellationToken cancellationToken = default); Task<TEntity?> FindByIdAsync(TId id, CancellationToken cancellationToken = default);
@ -25,7 +25,7 @@ public interface IReadRepository<TEntity, in TId>
} }
public interface IWriteRepository<TEntity, in TId> public interface IWriteRepository<TEntity, in TId>
where TEntity : class, IEntity<TId> where TEntity : class, IAggregate<TId>
{ {
Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default); Task<TEntity> AddAsync(TEntity entity, CancellationToken cancellationToken = default);
Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default); Task<TEntity> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
@ -39,11 +39,11 @@ public interface IRepository<TEntity, in TId> :
IReadRepository<TEntity, TId>, IReadRepository<TEntity, TId>,
IWriteRepository<TEntity, TId>, IWriteRepository<TEntity, TId>,
IDisposable IDisposable
where TEntity : class, IEntity<TId> where TEntity : class, IAggregate<TId>
{ {
} }
public interface IRepository<TEntity> : IRepository<TEntity, long> public interface IRepository<TEntity> : IRepository<TEntity, long>
where TEntity : class, IEntity<long> where TEntity : class, IAggregate<long>
{ {
} }

View File

@ -5,7 +5,7 @@ using MongoDB.Driver;
namespace BuildingBlocks.Mongo; namespace BuildingBlocks.Mongo;
public class MongoRepository<TEntity, TId> : IMongoRepository<TEntity, TId> public class MongoRepository<TEntity, TId> : IMongoRepository<TEntity, TId>
where TEntity : class, IEntity<TId> where TEntity : class, IAggregate<TId>
{ {
private readonly IMongoDbContext _context; private readonly IMongoDbContext _context;
protected readonly IMongoCollection<TEntity> DbSet; protected readonly IMongoCollection<TEntity> DbSet;

View File

@ -18,8 +18,9 @@ public sealed class EventMapper : IEventMapper
{ {
return @event switch return @event switch
{ {
FlightCreatedDomainEvent e => new FlightCreated(e.FlightNumber), FlightCreatedDomainEvent e => new FlightCreated(e.Id),
FlightUpdatedDomainEvent e => new FlightUpdated(e.FlightNumber), FlightUpdatedDomainEvent e => new FlightUpdated(e.Id),
FlightDeletedDomainEvent e => new FlightDeleted(e.Id),
AirportCreatedDomainEvent e => new AirportCreated(e.Id), AirportCreatedDomainEvent e => new AirportCreated(e.Id),
AircraftCreatedDomainEvent e => new AircraftCreated(e.Id), AircraftCreatedDomainEvent e => new AircraftCreated(e.Id),
_ => null _ => null

View File

@ -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;

View File

@ -33,7 +33,7 @@ public class CreateFlightCommandHandler : IRequestHandler<CreateFlightCommand, F
throw new FlightAlreadyExistException(); throw new FlightAlreadyExistException();
var flightEntity = Models.Flight.Create(command.Id, command.FlightNumber, command.AircraftId, command.DepartureAirportId, command.DepartureDate, var flightEntity = Models.Flight.Create(command.Id, command.FlightNumber, command.AircraftId, command.DepartureAirportId, command.DepartureDate,
command.ArriveDate, command.ArriveAirportId, command.DurationMinutes, command.FlightDate, FlightStatus.Completed, command.Price); command.ArriveDate, command.ArriveAirportId, command.DurationMinutes, command.FlightDate, command.Status, command.Price);
var newFlight = await _flightDbContext.Flights.AddAsync(flightEntity, cancellationToken); var newFlight = await _flightDbContext.Flights.AddAsync(flightEntity, cancellationToken);

View File

@ -0,0 +1,6 @@
using Flight.Flights.Dtos;
using MediatR;
namespace Flight.Flights.Features.DeleteFlight;
public record DeleteFlightCommand(long Id) : IRequest<FlightResponseDto>;

View File

@ -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<DeleteFlightCommand, FlightResponseDto>
{
private readonly FlightDbContext _flightDbContext;
private readonly IMapper _mapper;
public DeleteFlightCommandHandler(IMapper mapper, FlightDbContext flightDbContext)
{
_mapper = mapper;
_flightDbContext = flightDbContext;
}
public async Task<FlightResponseDto> 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<FlightResponseDto>(deleteFlight);
}
}

View File

@ -0,0 +1,14 @@
using FluentValidation;
namespace Flight.Flights.Features.DeleteFlight;
public class DeleteFlightCommandValidator : AbstractValidator<DeleteFlightCommand>
{
public DeleteFlightCommandValidator()
{
CascadeMode = CascadeMode.Stop;
RuleFor(x => x.Id).NotEmpty();
}
}

View File

@ -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<ActionResult> Update(DeleteFlightCommand command, CancellationToken cancellationToken)
{
var result = await Mediator.Send(command, cancellationToken);
return Ok(result);
}
}

View File

@ -35,7 +35,7 @@ public class UpdateFlightCommandHandler : IRequestHandler<UpdateFlightCommand, F
flight.Update(command.Id, command.FlightNumber, command.AircraftId, command.DepartureAirportId, command.DepartureDate, flight.Update(command.Id, command.FlightNumber, command.AircraftId, command.DepartureAirportId, command.DepartureDate,
command.ArriveDate, command.ArriveAirportId, command.DurationMinutes, command.FlightDate, FlightStatus.Completed, command.Price, command.IsDeleted); command.ArriveDate, command.ArriveAirportId, command.DurationMinutes, command.FlightDate, command.Status, command.Price, command.IsDeleted);
var updateFlight = _flightDbContext.Flights.Update(flight); var updateFlight = _flightDbContext.Flights.Update(flight);

View File

@ -71,4 +71,27 @@ public class Flight : Aggregate<long>
AddDomainEvent(@event); 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);
}
} }

View File

@ -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<AircraftCreated>()).Should().BeFalse();
(await _fixture.IsPublished<AircraftCreated>()).Should().BeTrue();
}
}

View File

@ -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<AirportCreated>()).Should().BeFalse();
(await _fixture.IsPublished<AirportCreated>()).Should().BeTrue();
}
}

View File

@ -0,0 +1,13 @@
using AutoBogus;
using BuildingBlocks.IdsGenerator;
using Flight.Aircrafts.Features.CreateAircraft;
namespace Integration.Test.Fakes;
public class FakeCreateAircraftCommand : AutoFaker<CreateAircraftCommand>
{
public FakeCreateAircraftCommand()
{
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
}
}

View File

@ -0,0 +1,13 @@
using AutoBogus;
using BuildingBlocks.IdsGenerator;
using Flight.Airports.Features.CreateAirport;
namespace Integration.Test.Fakes;
public class FakeCreateAirportCommand : AutoFaker<CreateAirportCommand>
{
public FakeCreateAirportCommand()
{
RuleFor(r => r.Id, _ => SnowFlakIdGenerator.NewId());
}
}

View File

@ -1,4 +1,5 @@
using AutoBogus; using AutoBogus;
using BuildingBlocks.IdsGenerator;
using Flight.Flights.Features.CreateFlight; using Flight.Flights.Features.CreateFlight;
namespace Integration.Test.Fakes; namespace Integration.Test.Fakes;
@ -7,7 +8,7 @@ public sealed class FakeCreateFlightCommand : AutoFaker<CreateFlightCommand>
{ {
public FakeCreateFlightCommand() 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.FlightNumber, r => r.Random.String());
RuleFor(r => r.DepartureAirportId, _ => 1); RuleFor(r => r.DepartureAirportId, _ => 1);
RuleFor(r => r.ArriveAirportId, _ => 2); RuleFor(r => r.ArriveAirportId, _ => 2);

View File

@ -21,11 +21,7 @@ public class CreateFlightTests
public async Task should_create_new_flight_to_db_and_publish_message_to_broker() public async Task should_create_new_flight_to_db_and_publish_message_to_broker()
{ {
// Arrange // Arrange
var fakeFlight = new FakeCreateFlightCommand().Generate(); var command = 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);
// Act // Act
var flightResponse = await _fixture.SendAsync(command); var flightResponse = await _fixture.SendAsync(command);

View File

@ -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<FlightDeleted>()).Should().BeFalse();
(await _fixture.IsPublished<FlightDeleted>()).Should().BeTrue();
}
}