add alpha implementation of route search

This commit is contained in:
cuqmbr 2023-12-04 15:43:10 +02:00
parent 806a139d3d
commit 7d3ad666c6
Signed by: cuqmbr
GPG Key ID: 2D72ED98B6CB200F
18 changed files with 1494 additions and 10 deletions

View File

@ -0,0 +1,14 @@
using AutobusApi.Application.RouteSearch;
using Microsoft.AspNetCore.Mvc;
namespace AutobusApi.Api.Controllers;
[Route("search")]
public class RouteSearchController : BaseController
{
[HttpPost]
public async Task<List<RouteWithTransfersDto>> Search([FromBody] RouteSearchQuery query, CancellationToken cancellationToken)
{
return await Mediator.Send(query, cancellationToken);
}
}

View File

@ -0,0 +1,18 @@
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
namespace AutobusApi.Api.OpenApi;
public class TimeSpanSchemaFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.Type == typeof(TimeSpan))
{
schema.Type = "string";
schema.Format = "time-span";
schema.Pattern = @"^\d{2}:\d{2}:\d{2}\.\d{3}$";
}
}
}

View File

@ -3,6 +3,8 @@ using AutobusApi.Application;
using AutobusApi.Api.Middlewares;
using AutoubsApi.Infrastructure.Data;
using AutobusApi.Infrastructure.Identity;
using Microsoft.OpenApi.Models;
using AutobusApi.Api.OpenApi;
var builder = WebApplication.CreateBuilder(args);
@ -11,7 +13,33 @@ builder.Services.AddApplication();
builder.Services.AddControllers();
builder.Services.AddSwaggerGen();
builder.Services.AddSwaggerGen(options =>
{
options.SchemaFilter<TimeSpanSchemaFilter>();
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Scheme = "Bearer",
BearerFormat = "Jwt",
In = ParameterLocation.Header,
Name = "Authorization",
Description = "Bearer Authentication With Jwt",
Type = SecuritySchemeType.Http
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Id = "Bearer",
Type = ReferenceType.SecurityScheme
}
},
new List<string>()
}
});
});
builder.Services.AddTransient<GlobalExceptionHandlerMiddleware>();

View File

@ -6,7 +6,7 @@
"Issuer": "",
"Audience": "",
"IssuerSigningKey": "a2c98dec80787a4e85ffb5bcbc24f7e4cc014d8a4fe43e9520480a50759164bc",
"AccessTokenValidityInMinutes": "5",
"AccessTokenValidityInMinutes": "60",
"RefreshTokenValidityInDays": "15",
}
}

View File

@ -19,6 +19,7 @@
<Version>7.0.14</Version>
</PackageReference>
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.7" />
<PackageReference Include="QuikGraph" Version="2.5.0" />
</ItemGroup>
<ItemGroup>

View File

@ -0,0 +1,22 @@
using MediatR.Pipeline;
using Microsoft.Extensions.Logging;
namespace AutobusApi.Application.Common.Behaviours;
public class LoggingBehaviour<TRequest> : IRequestPreProcessor<TRequest> where TRequest : notnull
{
private readonly ILogger _logger;
public LoggingBehaviour(ILogger<TRequest> logger)
{
_logger = logger;
}
public async Task Process(TRequest request, CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
_logger.LogInformation("Request: {Name} {@UserId} {@UserName} {@Request}",
requestName, request);
}
}

View File

@ -13,9 +13,9 @@ public class RegisterCommandHandler : IRequestHandler<RegisterCommand>
}
public async Task Handle(
RegisterCommand command,
RegisterCommand request,
CancellationToken cancellationToken)
{
await _identityService.RegisterAsync(command.Email, command.Password, cancellationToken);
await _identityService.RegisterAsync(request.Email, request.Password, cancellationToken);
}
}

View File

@ -14,9 +14,9 @@ public class RenewAccessTokenCommandHandler : IRequestHandler<RenewAccessTokenCo
}
public async Task<TokensModel> Handle(
RenewAccessTokenCommand command,
RenewAccessTokenCommand request,
CancellationToken cancellationToken)
{
return await _identityService.RenewAccessTokenAsync(command.RefreshToken, cancellationToken);
return await _identityService.RenewAccessTokenAsync(request.RefreshToken, cancellationToken);
}
}

View File

@ -13,9 +13,9 @@ public class RevokeRefreshTokenCommandHandler : IRequestHandler<RevokeRefreshTok
}
public async Task Handle(
RevokeRefreshTokenCommand command,
RevokeRefreshTokenCommand request,
CancellationToken cancellationToken)
{
await _identityService.RevokeRefreshTokenAsync(command.RefreshToken, cancellationToken);
await _identityService.RevokeRefreshTokenAsync(request.RefreshToken, cancellationToken);
}
}

View File

@ -14,9 +14,9 @@ public class LoginQueryHandler : IRequestHandler<LoginQuery, TokensModel>
}
public async Task<TokensModel> Handle(
LoginQuery query,
LoginQuery request,
CancellationToken cancellationToken)
{
return await _identityService.LoginAsync(query.Email, query.Password, cancellationToken);
return await _identityService.LoginAsync(request.Email, request.Password, cancellationToken);
}
}

View File

@ -0,0 +1,75 @@
using AutobusApi.Domain.Entities;
namespace AutobusApi.Application.RouteSearch;
public class EnrollmentAddressVertex
{
public EnrollmentAddressVertex(VehicleEnrollment vehicleEnrollment, Address address)
{
VehicleEnrollment = vehicleEnrollment;
Address = address;
}
public VehicleEnrollment VehicleEnrollment { get; set; }
public Address Address { get; set; }
public DateTime GetDepartureDateTimeUtc()
{
DateTime result = VehicleEnrollment.DepartureDateTimeUtc;
var rouetAddressDetails = VehicleEnrollment.RouteAddressDetails.OrderBy(rad => rad.RouteAddress.Order);
rouetAddressDetails.First().CurrentAddressStopTime = TimeSpan.Zero;
foreach (var rad in rouetAddressDetails)
{
result += rad.CurrentAddressStopTime;
if (rad.RouteAddress.Address.Id == Address.Id)
{
break;
}
result += rad.TimeToNextAddress;
}
return result;
}
public DateTime GetArrivalDateTimeUtc()
{
DateTime result = VehicleEnrollment.DepartureDateTimeUtc;
var rouetAddressDetails = VehicleEnrollment.RouteAddressDetails.OrderBy(rad => rad.RouteAddress.Order);
rouetAddressDetails.First().CurrentAddressStopTime = TimeSpan.Zero;
foreach (var rad in rouetAddressDetails)
{
if (rad.RouteAddress.Address.Id == Address.Id)
{
break;
}
result += rad.CurrentAddressStopTime;
result += rad.TimeToNextAddress;
}
return result;
}
public override bool Equals(object? obj)
{
if (obj == null || !(obj is EnrollmentAddressVertex))
{
return false;
}
EnrollmentAddressVertex other = (EnrollmentAddressVertex)obj;
return this.Address.Id == other.Address.Id;
}
public override int GetHashCode()
{
return Address.Id.GetHashCode();
}
}

View File

@ -0,0 +1,18 @@
using MediatR;
namespace AutobusApi.Application.RouteSearch;
public record RouteSearchQuery : IRequest<List<RouteWithTransfersDto>>
{
public required int DepartureAddressId { get; set; }
public required int ArrivalAddressId { get; set; }
public required DateOnly DepartureDate { get; set; }
public required TimeSpan MinTransferTime { get; set; } = TimeSpan.FromMinutes(15);
public required TimeSpan MaxTransferTime { get; set; } = TimeSpan.FromHours(12);
public required double MaxTransferDistanceInMeters { get; set; } = 1000;
}

View File

@ -0,0 +1,220 @@
using AutobusApi.Application.Common.Exceptions;
using AutobusApi.Application.Common.Interfaces;
using AutoMapper;
using AutoMapper.QueryableExtensions;
using FluentValidation.Results;
using MediatR;
using Microsoft.EntityFrameworkCore;
using QuikGraph;
namespace AutobusApi.Application.RouteSearch;
public class RouteSearchQueryHandler : IRequestHandler<RouteSearchQuery, List<RouteWithTransfersDto>>
{
private readonly IApplicationDbContext _dbContext;
private readonly IMapper _mapper;
public RouteSearchQueryHandler(
IApplicationDbContext dbContext,
IMapper mapper)
{
_dbContext = dbContext;
_mapper = mapper;
}
public async Task<List<RouteWithTransfersDto>> Handle(
RouteSearchQuery request,
CancellationToken cancellationToken)
{
var departureEnrollmentAddressVertex = await GetEnrollmentAddressVertexAsync(request.DepartureAddressId);
var arrivalEnrollmentAddressVertex = await GetEnrollmentAddressVertexAsync(request.ArrivalAddressId);
var graph = await InitializeGraphAsync(request.DepartureDate, cancellationToken);
var paths = FindAllPaths(graph, departureEnrollmentAddressVertex, arrivalEnrollmentAddressVertex,
request.MinTransferTime, request.MaxTransferTime, request.MaxTransferDistanceInMeters);
return paths.AsQueryable().ProjectTo<RouteWithTransfersDto>(_mapper.ConfigurationProvider).ToList();
}
private async Task<AdjacencyGraph<EnrollmentAddressVertex, SEquatableEdge<EnrollmentAddressVertex>>> InitializeGraphAsync(
DateOnly departureDate,
CancellationToken cancellationToken)
{
var graph = new AdjacencyGraph<EnrollmentAddressVertex, SEquatableEdge<EnrollmentAddressVertex>>();
var vehicleEnrollmentsArray = await _dbContext.VehicleEnrollments
.Include(ve => ve.Route)
.ThenInclude(r => r.RouteAddresses)
.ThenInclude(ra => ra.Address)
.ThenInclude(a => a.City)
.ThenInclude(c => c.Region)
.ThenInclude(r => r.Country)
.Include(ve => ve.Route)
.ThenInclude(r => r.RouteAddresses)
.ThenInclude(ra => ra.RouteAddressDetails)
.Include(ve => ve.Tickets)
.ThenInclude(t => t.TicketGroup)
.Include(ve => ve.Vehicle)
.ThenInclude(v => v.Company)
.Where(ve =>
DateOnly.FromDateTime(ve.DepartureDateTimeUtc) >= departureDate &&
DateOnly.FromDateTime(ve.DepartureDateTimeUtc) < departureDate.AddDays(1))
.ToArrayAsync(cancellationToken);
foreach (var enrollment in vehicleEnrollmentsArray)
{
var routeAddressesArray = enrollment.Route.RouteAddresses.OrderBy(ra => ra.Order).ToArray();
var previousVertex = new EnrollmentAddressVertex(enrollment, routeAddressesArray[0].Address);
graph.AddVertex(previousVertex);
for (int i = 1; i < routeAddressesArray.Length; i++)
{
var currentVertex = new EnrollmentAddressVertex(enrollment, routeAddressesArray[i].Address);
graph.AddVertex(currentVertex);
graph.AddEdge(new SEquatableEdge<EnrollmentAddressVertex>(previousVertex, currentVertex));
var vertexFromDifferentRoute = graph.Vertices.FirstOrDefault(v => v.Address.Id == currentVertex.Address.Id);
if (vertexFromDifferentRoute != null)
{
graph.AddEdge(new SEquatableEdge<EnrollmentAddressVertex>(currentVertex, vertexFromDifferentRoute));
}
previousVertex = currentVertex;
}
}
return graph;
}
private async Task<EnrollmentAddressVertex> GetEnrollmentAddressVertexAsync(int addressId)
{
var routeAddress = await _dbContext.VehicleEnrollments
.Include(ve => ve.Route)
.ThenInclude(r => r.RouteAddresses)
.ThenInclude(ra => ra.Address)
.ThenInclude(a => a.City)
.ThenInclude(c => c.Region)
.ThenInclude(r => r.Country)
.Include(ve => ve.Route)
.ThenInclude(r => r.RouteAddresses)
.ThenInclude(ra => ra.RouteAddressDetails)
.Include(ve => ve.Tickets)
.ThenInclude(t => t.TicketGroup)
.Include(ve => ve.Vehicle)
.ThenInclude(v => v.Company)
.SelectMany(ve => ve.Route.RouteAddresses)
.FirstOrDefaultAsync(ra => ra.Address.Id == addressId);
if (routeAddress == null)
{
throw new ValidationException(new ValidationFailure[] { new ValidationFailure("AddressId", "Departure or Arrival Address with given Id is not found") });
}
return new EnrollmentAddressVertex(null, routeAddress.Address);
}
private List<List<EnrollmentAddressVertex>> FindAllPaths(
AdjacencyGraph<EnrollmentAddressVertex, SEquatableEdge<EnrollmentAddressVertex>> graph,
EnrollmentAddressVertex startVertex,
EnrollmentAddressVertex endVertex,
TimeSpan minTransferTime,
TimeSpan maxTransferTime,
double maxTransferDistanceInMeters)
{
var paths = new List<List<EnrollmentAddressVertex>>();
var visited = new HashSet<EnrollmentAddressVertex>();
var currentPath = new List<EnrollmentAddressVertex>();
FindAllPathsDFS(graph, startVertex, endVertex, visited, currentPath, paths, minTransferTime,
maxTransferTime, maxTransferDistanceInMeters);
return paths;
}
private void FindAllPathsDFS(
AdjacencyGraph<EnrollmentAddressVertex, SEquatableEdge<EnrollmentAddressVertex>> graph,
EnrollmentAddressVertex current,
EnrollmentAddressVertex endVertex,
HashSet<EnrollmentAddressVertex> visited,
List<EnrollmentAddressVertex> currentPath,
List<List<EnrollmentAddressVertex>> paths,
TimeSpan minTransferTime,
TimeSpan maxTransferTime,
double maxTransferDistanceInMeters)
{
visited.Add(current);
currentPath.Add(current);
if (current.Equals(endVertex))
{
paths.Add(new List<EnrollmentAddressVertex>(currentPath));
}
else
{
foreach (var edge in graph.OutEdges(current))
{
if (!visited.Contains(edge.Target))
{
if (current.VehicleEnrollment == null)
{
current.VehicleEnrollment = edge.Target.VehicleEnrollment;
}
// If transfering to other vehicle enrollment
if (current.VehicleEnrollment.Id != edge.Target.VehicleEnrollment.Id)
{
// Skip paths where there is a singular vertex from any Vehicle Enrollment
// A - A - A - _B_ <- - C - C
var threeLast = currentPath.TakeLast(3).ToArray();
if (currentPath.Count >= 4 &&
threeLast[0].VehicleEnrollment.Id != threeLast[1].VehicleEnrollment.Id &&
threeLast[1].VehicleEnrollment.Id != threeLast[2].VehicleEnrollment.Id)
{
continue;
}
// Filter by amount of free time between enrollment 1 arrives and enrollment 2 departures
var freeTime = edge.Target.GetDepartureDateTimeUtc() - current.GetArrivalDateTimeUtc();
if (freeTime < minTransferTime ||
freeTime > maxTransferTime)
{
continue;
}
// Filter by distance between transfer locations
var currentLocation = current.Address.Location;
var targetLocation = edge.Target.Address.Location;
const double metersInDegree = 111_111.111;
var distanceInDegrees = Math.Sqrt(
Math.Pow(currentLocation.Latitude - targetLocation.Latitude, 2) +
Math.Pow(currentLocation.Longitude - targetLocation.Longitude, 2));
var distanceInMeters = distanceInDegrees * metersInDegree;
if (distanceInMeters > maxTransferDistanceInMeters)
{
continue;
}
}
FindAllPathsDFS(graph, edge.Target, endVertex, visited, currentPath, paths,
minTransferTime, maxTransferTime, maxTransferDistanceInMeters);
}
}
}
visited.Remove(current);
currentPath.RemoveAt(currentPath.Count - 1);
}
}

View File

@ -0,0 +1,13 @@
using FluentValidation;
namespace AutobusApi.Application.RouteSearch;
public class RouteSearchQueryValidator : AbstractValidator<RouteSearchQuery>
{
public RouteSearchQueryValidator()
{
// RuleFor(v => v.Email)
// .NotEmpty().WithMessage("Email address is required.")
// .EmailAddress().WithMessage("Email address is invalid.");
}
}

View File

@ -0,0 +1,143 @@
using AutobusApi.Application.Common.Mappings;
using AutoMapper;
namespace AutobusApi.Application.RouteSearch;
public class RouteWithTransfersDto : IMapFrom<List<EnrollmentAddressVertex>>
{
public List<SubRouteDto> Routes { get; set; } = null!;
public TimeSpan TotalTravelTime { get; set; }
public double TotalCost { get; set; }
// public int NumberOfTransfers { get; set; }
public DateTime DeparuteDateTime { get; set; }
public DateTime ArrivalDateTime { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<List<EnrollmentAddressVertex>, RouteWithTransfersDto>()
.ForMember(
d => d.TotalCost,
opt => opt.MapFrom(
s => s
.SkipLast(1)
.Select(eav => eav.VehicleEnrollment)
.SelectMany(ve => ve.RouteAddressDetails)
.Select(rad => rad.CostToNextAddress)
.Aggregate(0.0, (sum, val) => sum + val)
)
)
.ForMember(
d => d.TotalTravelTime,
opt => opt.MapFrom(
s =>
s.Last().VehicleEnrollment.GetArrivalDateTimeUtc(s.Last().Address.Id) -
s.First().VehicleEnrollment.GetDepartureDateTimeUtc(s.First().Address.Id)
)
)
.ForMember(d => d.DeparuteDateTime, opt => opt.MapFrom(s => s.First().VehicleEnrollment.GetDepartureDateTimeUtc(s.First().Address.Id)))
.ForMember(d => d.ArrivalDateTime, opt => opt.MapFrom(s => s.Last().VehicleEnrollment.GetArrivalDateTimeUtc(s.Last().Address.Id)))
.ForMember(d => d.Routes, opt => opt.MapFrom(s => CreateRoutes(s)));
}
List<SubRouteDto> CreateRoutes(List<EnrollmentAddressVertex> vertices)
{
var result = new List<SubRouteDto>();
var groupedByVehicle = vertices.GroupBy(ev => ev.VehicleEnrollment);
int order = 1;
foreach (var group in groupedByVehicle)
{
var subRouteDto = new SubRouteDto
{
Stations = new List<StationDto>(),
VehicleType = "Unknown",//group.Key.Vehicle.Type, // Replace with the actual property to fetch VehicleType
CompanyName = group.Key.Vehicle.Company.Name, // Replace with the actual property to fetch CompanyName
CompanyId = group.Key.Vehicle.Company.Id, // Replace with the actual property to fetch CompanyId
Order = order
// Other properties initialization
};
order++;
foreach (var enrollmentVertex in group)
{
var stationDto = new StationDto
{
AddressId = enrollmentVertex.Address.Id,
AddressName = enrollmentVertex.Address.Name,
CityId = enrollmentVertex.Address.CityId,
CityName = enrollmentVertex.Address.City.Name,
RegionId = enrollmentVertex.Address.City.RegionId,
RegionName = enrollmentVertex.Address.City.Region.Name,
CountryId = enrollmentVertex.Address.City.Region.CountryId,
CountryName = enrollmentVertex.Address.City.Region.Country.Name,
DeparureTime = enrollmentVertex.GetDepartureDateTimeUtc(),
ArrivalTime = enrollmentVertex.GetArrivalDateTimeUtc(),
StopTime = enrollmentVertex.GetDepartureDateTimeUtc() - enrollmentVertex.GetArrivalDateTimeUtc(),
Latitude = enrollmentVertex.Address.Location.Latitude,
Longitude = enrollmentVertex.Address.Location.Longitude
};
subRouteDto.Stations.Add(stationDto);
}
result.Add(subRouteDto);
}
return result;
}
}
public class SubRouteDto
{
public List<StationDto> Stations { get; set; } = null!;
public string VehicleType { get; set; } = null!;
public string CompanyName { get; set; } = null!;
public int CompanyId { get; set; }
public int TicketsAvailable { get; set; }
public int TicketsBought { get; set; }
public int Order { get; set; }
}
public class StationDto : IMapFrom<EnrollmentAddressVertex>
{
public int AddressId { get; set; }
public string AddressName { get; set; } = null!;
public int CityId { get; set; }
public string CityName { get; set; } = null!;
public int RegionId { get; set; }
public string RegionName { get; set; } = null!;
public int CountryId { get; set; }
public string CountryName { get; set; } = null!;
public DateTime DeparureTime { get; set; }
public DateTime ArrivalTime { get; set; }
public TimeSpan StopTime { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
}

View File

@ -1,4 +1,6 @@
using AutobusApi.Domain.Entities;
using AutobusApi.Domain.Enums;
using AutobusApi.Infrastructure.Data.Entities;
using AutobusApi.Infrastructure.Identity;
using AutoubsApi.Infrastructure.Data;
using Microsoft.AspNetCore.Identity;
@ -46,6 +48,454 @@ public static class DbInitializer
private static void InitializeDomain(ApplicationDbContext dbContext)
{
var country1 = new Country()
{
Name = "Country A"
};
dbContext.Countries.AddRange(new[] { country1 });
dbContext.SaveChanges();
var region1 = new Region()
{
Name = "Region A",
Country = country1
};
dbContext.Regions.AddRange(new[] { region1 });
dbContext.SaveChanges();
var city1 = new City()
{
Name = "City A",
Region = region1
};
var city2 = new City()
{
Name = "City B",
Region = region1
};
var city3 = new City()
{
Name = "City C",
Region = region1
};
var city4 = new City()
{
Name = "City D",
Region = region1
};
var city5 = new City()
{
Name = "City E",
Region = region1
};
var city6 = new City()
{
Name = "City F",
Region = region1
};
var city7 = new City()
{
Name = "City G",
Region = region1
};
var city8 = new City()
{
Name = "City H",
Region = region1
};
var city9 = new City()
{
Name = "City I",
Region = region1
};
var city10 = new City()
{
Name = "City J",
Region = region1
};
dbContext.Cities.AddRange(new[] { city1, city2, city3, city4, city5, city6, city7, city8, city9, city10 });
dbContext.SaveChanges();
var address1 = new Address()
{
Name = "address A",
Location = new Coordinates(11, 11),
VehicleType = VehicleType.Bus,
City = city1
};
var address2 = new Address()
{
Name = "address B",
Location = new Coordinates(22, 22),
VehicleType = VehicleType.Bus,
City = city2
};
var address3 = new Address()
{
Name = "address C",
Location = new Coordinates(33, 33),
VehicleType = VehicleType.Bus,
City = city3
};
var address4 = new Address()
{
Name = "address E",
Location = new Coordinates(44, 44),
VehicleType = VehicleType.Bus,
City = city4
};
var address5 = new Address()
{
Name = "address F",
Location = new Coordinates(55, 55),
VehicleType = VehicleType.Bus,
City = city5
};
var address6 = new Address()
{
Name = "address G",
Location = new Coordinates(66, 66),
VehicleType = VehicleType.Bus,
City = city6
};
var address7 = new Address()
{
Name = "address H",
Location = new Coordinates(77, 77),
VehicleType = VehicleType.Bus,
City = city7
};
var address8 = new Address()
{
Name = "address I",
Location = new Coordinates(88, 88),
VehicleType = VehicleType.Bus,
City = city8
};
var address9 = new Address()
{
Name = "address J",
Location = new Coordinates(99, 99),
VehicleType = VehicleType.Bus,
City = city9
};
var address10 = new Address()
{
Name = "address K",
Location = new Coordinates(110, 110),
VehicleType = VehicleType.Bus,
City = city10
};
dbContext.Addresses.AddRange(new[] { address1, address2, address3, address4, address5, address6, address7, address8, address9, address10 });
dbContext.SaveChanges();
var route1 = new Route()
{
RouteAddresses = new RouteAddress[]
{
new RouteAddress
{
Order = 1,
Address = address1
},
new RouteAddress
{
Order = 2,
Address = address2
},
new RouteAddress
{
Order = 3,
Address = address3
},
new RouteAddress
{
Order = 4,
Address = address4
},
new RouteAddress
{
Order = 5,
Address = address5
},
new RouteAddress
{
Order = 6,
Address = address8
}
}
};
var route2 = new Route()
{
RouteAddresses = new RouteAddress[]
{
new RouteAddress
{
Order = 1,
Address = address2
},
new RouteAddress
{
Order = 2,
Address = address3
},
new RouteAddress
{
Order = 3,
Address = address6
},
new RouteAddress
{
Order = 4,
Address = address7
}
}
};
var route3 = new Route()
{
RouteAddresses = new RouteAddress[]
{
new RouteAddress
{
Order = 1,
Address = address6
},
new RouteAddress
{
Order = 2,
Address = address8
},
new RouteAddress
{
Order = 3,
Address = address9
},
new RouteAddress
{
Order = 4,
Address = address10
}
}
};
dbContext.Routes.AddRange(new[] { route1, route2, route3 });
dbContext.SaveChanges();
var company1 = new Company
{
Name = "Smartlines",
LegalAddress = "1234 Spectrum Avenue, Metroville, XYZ 98765, United States",
ContactEmail = "support@smartlines.com",
ContactPhoneNumber = "+00000000000"
};
var company2 = new Company
{
Name = "ecobus",
LegalAddress = "5678 Galaxy Street, Cosmic City, ABC 54321, United States",
ContactEmail = "support@ecobus.com",
ContactPhoneNumber = "+00000000000"
};
dbContext.Companies.AddRange(new[] { company1, company2 });
dbContext.SaveChanges();
var vehicle1 = new Bus()
{
Number = "AC8236HK",
Model = "Mercedes-Benz Citaro",
Capacity = 20,
HasWC = true,
HasWiFi = false,
HasOutlets = true,
HasMultimedia = false,
HasClimateControl = true,
Company = company1
};
var vehicle2 = new Bus()
{
Number = "EP2566MK",
Model = "Van Hool TDX25",
Capacity = 25,
HasWC = true,
HasWiFi = false,
HasOutlets = true,
HasMultimedia = false,
HasClimateControl = true,
Company = company2
};
var vehicle3 = new Bus()
{
Number = "OK8634MC",
Model = "New Flyer Xcelsior",
Capacity = 30,
HasWC = true,
HasWiFi = false,
HasOutlets = true,
HasMultimedia = false,
HasClimateControl = true,
Company = company1
};
dbContext.Vehicles.AddRange(new[] { vehicle1, vehicle2, vehicle3 });
dbContext.SaveChanges();
var vehicleEnrollment1 = new VehicleEnrollment()
{
DepartureDateTimeUtc = DateTime.UtcNow,
Vehicle = vehicle1,
Route = route1,
RouteAddressDetails = new RouteAddressDetails[]
{
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(50),
CostToNextAddress = 25,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[0],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(40),
CostToNextAddress = 20,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[1],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(45),
CostToNextAddress = 23.33,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[2],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(25),
CostToNextAddress = 15,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[3],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(30),
CostToNextAddress = 17.5,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[4],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(35),
CostToNextAddress = 19,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[5],
}
}
};
var vehicleEnrollment2 = new VehicleEnrollment()
{
DepartureDateTimeUtc = DateTime.UtcNow.AddMinutes(30),
Vehicle = vehicle2,
Route = route2,
RouteAddressDetails = new RouteAddressDetails[]
{
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(40),
CostToNextAddress = 25,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[0],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(50),
CostToNextAddress = 20,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[1],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(45),
CostToNextAddress = 23.33,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[2],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(25),
CostToNextAddress = 15,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[3],
}
}
};
var vehicleEnrollment3 = new VehicleEnrollment()
{
DepartureDateTimeUtc = DateTime.UtcNow.AddMinutes(175),
Vehicle = vehicle3,
Route = route3,
RouteAddressDetails = new RouteAddressDetails[]
{
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(50),
CostToNextAddress = 25,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[0],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(40),
CostToNextAddress = 20,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[1],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(45),
CostToNextAddress = 23.33,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[2],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(25),
CostToNextAddress = 15,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[3],
}
}
};
dbContext.VehicleEnrollments.AddRange(new[] { vehicleEnrollment1, vehicleEnrollment2/* , vehicleEnrollment3 */ });
dbContext.SaveChanges();
}
private static void InitializeIdentity(ApplicationIdentityDbContext identityDbContext)

View File

@ -1,5 +1,9 @@
using AutobusApi.Domain.Entities;
using AutobusApi.Domain.Enums;
using AutobusApi.Infrastructure.Data.Entities;
using AutobusApi.Infrastructure.Identity;
using AutoubsApi.Infrastructure.Data;
using Route = AutobusApi.Domain.Entities.Route;
namespace AutobusApi.IntegrationTests;
@ -14,7 +18,454 @@ public static class DbInitializer
private static void InitializeDomain(ApplicationDbContext dbContext)
{
var country1 = new Country()
{
Name = "Country A"
};
dbContext.Countries.AddRange(new[] { country1 });
dbContext.SaveChanges();
var region1 = new Region()
{
Name = "Region A",
Country = country1
};
dbContext.Regions.AddRange(new[] { region1 });
dbContext.SaveChanges();
var city1 = new City()
{
Name = "City A",
Region = region1
};
var city2 = new City()
{
Name = "City B",
Region = region1
};
var city3 = new City()
{
Name = "City C",
Region = region1
};
var city4 = new City()
{
Name = "City D",
Region = region1
};
var city5 = new City()
{
Name = "City E",
Region = region1
};
var city6 = new City()
{
Name = "City F",
Region = region1
};
var city7 = new City()
{
Name = "City G",
Region = region1
};
var city8 = new City()
{
Name = "City H",
Region = region1
};
var city9 = new City()
{
Name = "City I",
Region = region1
};
var city10 = new City()
{
Name = "City J",
Region = region1
};
dbContext.Cities.AddRange(new[] { city1, city2, city3, city4, city5, city6, city7, city8, city9, city10 });
dbContext.SaveChanges();
var address1 = new Address()
{
Name = "address A",
Location = new Coordinates(11, 11),
VehicleType = VehicleType.Bus,
City = city1
};
var address2 = new Address()
{
Name = "address B",
Location = new Coordinates(22, 22),
VehicleType = VehicleType.Bus,
City = city2
};
var address3 = new Address()
{
Name = "address C",
Location = new Coordinates(33, 33),
VehicleType = VehicleType.Bus,
City = city3
};
var address4 = new Address()
{
Name = "address E",
Location = new Coordinates(44, 44),
VehicleType = VehicleType.Bus,
City = city4
};
var address5 = new Address()
{
Name = "address F",
Location = new Coordinates(55, 55),
VehicleType = VehicleType.Bus,
City = city5
};
var address6 = new Address()
{
Name = "address G",
Location = new Coordinates(66, 66),
VehicleType = VehicleType.Bus,
City = city6
};
var address7 = new Address()
{
Name = "address H",
Location = new Coordinates(77, 77),
VehicleType = VehicleType.Bus,
City = city7
};
var address8 = new Address()
{
Name = "address I",
Location = new Coordinates(88, 88),
VehicleType = VehicleType.Bus,
City = city8
};
var address9 = new Address()
{
Name = "address J",
Location = new Coordinates(99, 99),
VehicleType = VehicleType.Bus,
City = city9
};
var address10 = new Address()
{
Name = "address K",
Location = new Coordinates(110, 110),
VehicleType = VehicleType.Bus,
City = city10
};
dbContext.Addresses.AddRange(new[] { address1, address2, address3, address4, address5, address6, address7, address8, address9, address10 });
dbContext.SaveChanges();
var route1 = new Route()
{
RouteAddresses = new RouteAddress[]
{
new RouteAddress
{
Order = 1,
Address = address1
},
new RouteAddress
{
Order = 2,
Address = address2
},
new RouteAddress
{
Order = 3,
Address = address3
},
new RouteAddress
{
Order = 4,
Address = address4
},
new RouteAddress
{
Order = 5,
Address = address5
},
new RouteAddress
{
Order = 6,
Address = address8
}
}
};
var route2 = new Route()
{
RouteAddresses = new RouteAddress[]
{
new RouteAddress
{
Order = 1,
Address = address2
},
new RouteAddress
{
Order = 2,
Address = address3
},
new RouteAddress
{
Order = 3,
Address = address6
},
new RouteAddress
{
Order = 4,
Address = address7
}
}
};
var route3 = new Route()
{
RouteAddresses = new RouteAddress[]
{
new RouteAddress
{
Order = 1,
Address = address6
},
new RouteAddress
{
Order = 2,
Address = address8
},
new RouteAddress
{
Order = 3,
Address = address9
},
new RouteAddress
{
Order = 4,
Address = address10
}
}
};
dbContext.Routes.AddRange(new[] { route1, route2, route3 });
dbContext.SaveChanges();
var company1 = new Company
{
Name = "Smartlines",
LegalAddress = "1234 Spectrum Avenue, Metroville, XYZ 98765, United States",
ContactEmail = "support@smartlines.com",
ContactPhoneNumber = "+00000000000"
};
var company2 = new Company
{
Name = "ecobus",
LegalAddress = "5678 Galaxy Street, Cosmic City, ABC 54321, United States",
ContactEmail = "support@ecobus.com",
ContactPhoneNumber = "+00000000000"
};
dbContext.Companies.AddRange(new[] { company1, company2 });
dbContext.SaveChanges();
var vehicle1 = new Bus()
{
Number = "AC8236HK",
Model = "Mercedes-Benz Citaro",
Capacity = 20,
HasWC = true,
HasWiFi = false,
HasOutlets = true,
HasMultimedia = false,
HasClimateControl = true,
Company = company1
};
var vehicle2 = new Bus()
{
Number = "EP2566MK",
Model = "Van Hool TDX25",
Capacity = 25,
HasWC = true,
HasWiFi = false,
HasOutlets = true,
HasMultimedia = false,
HasClimateControl = true,
Company = company2
};
var vehicle3 = new Bus()
{
Number = "OK8634MC",
Model = "New Flyer Xcelsior",
Capacity = 30,
HasWC = true,
HasWiFi = false,
HasOutlets = true,
HasMultimedia = false,
HasClimateControl = true,
Company = company1
};
dbContext.Vehicles.AddRange(new[] { vehicle1, vehicle2, vehicle3 });
dbContext.SaveChanges();
var vehicleEnrollment1 = new VehicleEnrollment()
{
DepartureDateTimeUtc = DateTime.UtcNow,
Vehicle = vehicle1,
Route = route1,
RouteAddressDetails = new RouteAddressDetails[]
{
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(50),
CostToNextAddress = 25,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[0],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(40),
CostToNextAddress = 20,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[1],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(45),
CostToNextAddress = 23.33,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[2],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(25),
CostToNextAddress = 15,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[3],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(30),
CostToNextAddress = 17.5,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[4],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(35),
CostToNextAddress = 19,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route1.RouteAddresses.ToArray()[5],
}
}
};
var vehicleEnrollment2 = new VehicleEnrollment()
{
DepartureDateTimeUtc = DateTime.UtcNow.AddMinutes(30),
Vehicle = vehicle2,
Route = route2,
RouteAddressDetails = new RouteAddressDetails[]
{
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(40),
CostToNextAddress = 25,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[0],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(50),
CostToNextAddress = 20,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[1],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(45),
CostToNextAddress = 23.33,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[2],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(25),
CostToNextAddress = 15,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[3],
}
}
};
var vehicleEnrollment3 = new VehicleEnrollment()
{
DepartureDateTimeUtc = DateTime.UtcNow.AddMinutes(175),
Vehicle = vehicle3,
Route = route3,
RouteAddressDetails = new RouteAddressDetails[]
{
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(50),
CostToNextAddress = 25,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[0],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(40),
CostToNextAddress = 20,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[1],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(45),
CostToNextAddress = 23.33,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[2],
},
new RouteAddressDetails()
{
TimeToNextAddress = TimeSpan.FromMinutes(25),
CostToNextAddress = 15,
CurrentAddressStopTime = TimeSpan.FromMinutes(5),
RouteAddress = route2.RouteAddresses.ToArray()[3],
}
}
};
dbContext.VehicleEnrollments.AddRange(new[] { vehicleEnrollment1, vehicleEnrollment2, vehicleEnrollment3 });
dbContext.SaveChanges();
}
private static void InitializeIdentity(ApplicationIdentityDbContext identityDbContext)

View File

@ -0,0 +1,31 @@
using System.Net;
using Newtonsoft.Json;
namespace AutobusApi.IntegrationTests.Tests;
public class RouteSearchTests : TestsBase
{
public RouteSearchTests(CustomWebApplicationFactory<Program> factory)
: base(factory) {}
[Theory]
[InlineData(1, 10)]
public async Task RouteSearch_ValidStationsAndDate_Returns200OK(int fromId, int toId)
{
var query = new
{
DepartureAddressId = fromId,
ArrivalAddressId = toId,
DepartureDate = DateOnly.FromDateTime(DateTime.UtcNow.Date),
MinTransferTime = TimeSpan.FromMinutes(15),
MaxTransferTime = TimeSpan.FromHours(12),
MaxTransferDistanceInMeters = 1000
};
var response = await _httpClient.PostAsJsonAsync("search", query);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var content = JsonConvert.DeserializeObject<dynamic>(await response.Content.ReadAsStringAsync());
}
}