mirror of
https://github.com/meysamhadeli/booking-microservices.git
synced 2026-04-15 22:04:05 +08:00
add full integration tests for flight services
This commit is contained in:
parent
c7c9b2dd9e
commit
f325dd18ee
@ -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;
|
||||
|
||||
@ -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();
|
||||
public IReadOnlyList<IDomainEvent> 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; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -3,12 +3,11 @@ using BuildingBlocks.EventStoreDB.Events;
|
||||
|
||||
namespace BuildingBlocks.Domain.Model
|
||||
{
|
||||
public interface IAggregate : IProjection, IAuditable
|
||||
public interface IAggregate : IProjection, IEntity
|
||||
{
|
||||
IReadOnlyList<IDomainEvent> DomainEvents { get; }
|
||||
IEvent[] ClearDomainEvents();
|
||||
long Version { get; }
|
||||
public bool IsDeleted { get; }
|
||||
}
|
||||
|
||||
public interface IAggregate<out T> : IAggregate
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
|
||||
@ -2,10 +2,9 @@ namespace BuildingBlocks.Domain.Model;
|
||||
|
||||
public interface IEntity
|
||||
{
|
||||
}
|
||||
|
||||
public interface IEntity<out TId>
|
||||
{
|
||||
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; }
|
||||
}
|
||||
|
||||
@ -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<IAggregate>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,12 +56,12 @@ public class EfTxBehavior<TRequest, TResponse> : IPipelineBehavior<TRequest, TRe
|
||||
nameof(EfTxBehavior<TRequest, TResponse>),
|
||||
typeof(TRequest).FullName);
|
||||
|
||||
await _dbContextBase.CommitTransactionAsync(cancellationToken);
|
||||
|
||||
var domainEvents = _dbContextBase.GetDomainEvents();
|
||||
|
||||
await _busPublisher.SendAsync(domainEvents.ToArray(), cancellationToken);
|
||||
|
||||
await _dbContextBase.CommitTransactionAsync(cancellationToken);
|
||||
|
||||
return response;
|
||||
}
|
||||
catch
|
||||
|
||||
@ -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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,6 @@ using BuildingBlocks.Domain.Model;
|
||||
namespace BuildingBlocks.Mongo;
|
||||
|
||||
public interface IMongoRepository<TEntity, in TId> : IRepository<TEntity, TId>
|
||||
where TEntity : class, IEntity<TId>
|
||||
where TEntity : class, IAggregate<TId>
|
||||
{
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ using BuildingBlocks.Domain.Model;
|
||||
namespace BuildingBlocks.Mongo;
|
||||
|
||||
public interface IReadRepository<TEntity, in TId>
|
||||
where TEntity : class, IEntity<TId>
|
||||
where TEntity : class, IAggregate<TId>
|
||||
{
|
||||
Task<TEntity?> FindByIdAsync(TId id, CancellationToken cancellationToken = default);
|
||||
|
||||
@ -25,7 +25,7 @@ public interface IReadRepository<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> UpdateAsync(TEntity entity, CancellationToken cancellationToken = default);
|
||||
@ -39,11 +39,11 @@ public interface IRepository<TEntity, in TId> :
|
||||
IReadRepository<TEntity, TId>,
|
||||
IWriteRepository<TEntity, TId>,
|
||||
IDisposable
|
||||
where TEntity : class, IEntity<TId>
|
||||
where TEntity : class, IAggregate<TId>
|
||||
{
|
||||
}
|
||||
|
||||
public interface IRepository<TEntity> : IRepository<TEntity, long>
|
||||
where TEntity : class, IEntity<long>
|
||||
where TEntity : class, IAggregate<long>
|
||||
{
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ using MongoDB.Driver;
|
||||
namespace BuildingBlocks.Mongo;
|
||||
|
||||
public class MongoRepository<TEntity, TId> : IMongoRepository<TEntity, TId>
|
||||
where TEntity : class, IEntity<TId>
|
||||
where TEntity : class, IAggregate<TId>
|
||||
{
|
||||
private readonly IMongoDbContext _context;
|
||||
protected readonly IMongoCollection<TEntity> DbSet;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
@ -33,7 +33,7 @@ public class CreateFlightCommandHandler : IRequestHandler<CreateFlightCommand, F
|
||||
throw new FlightAlreadyExistException();
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
using Flight.Flights.Dtos;
|
||||
using MediatR;
|
||||
|
||||
namespace Flight.Flights.Features.DeleteFlight;
|
||||
|
||||
public record DeleteFlightCommand(long Id) : IRequest<FlightResponseDto>;
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,7 +35,7 @@ public class UpdateFlightCommandHandler : IRequestHandler<UpdateFlightCommand, F
|
||||
|
||||
|
||||
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);
|
||||
|
||||
|
||||
@ -71,4 +71,27 @@ public class Flight : Aggregate<long>
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
34
src/Services/Flight/tests/Aircraft/CreateAircraftTests.cs
Normal file
34
src/Services/Flight/tests/Aircraft/CreateAircraftTests.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
34
src/Services/Flight/tests/Airport/CreateAirportTests.cs
Normal file
34
src/Services/Flight/tests/Airport/CreateAirportTests.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
13
src/Services/Flight/tests/Fakes/FakeCreateAircraftCommand.cs
Normal file
13
src/Services/Flight/tests/Fakes/FakeCreateAircraftCommand.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
13
src/Services/Flight/tests/Fakes/FakeCreateAirportCommand.cs
Normal file
13
src/Services/Flight/tests/Fakes/FakeCreateAirportCommand.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
@ -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<CreateFlightCommand>
|
||||
{
|
||||
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);
|
||||
|
||||
@ -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);
|
||||
|
||||
50
src/Services/Flight/tests/Flight/DeleteFlightTests.cs
Normal file
50
src/Services/Flight/tests/Flight/DeleteFlightTests.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user