diff --git a/src/OpenFTTH.GDBIntegrator.Config/ErrorCode.cs b/src/OpenFTTH.GDBIntegrator.Config/ErrorCode.cs new file mode 100644 index 0000000..cf3ef9e --- /dev/null +++ b/src/OpenFTTH.GDBIntegrator.Config/ErrorCode.cs @@ -0,0 +1,22 @@ +namespace OpenFTTH.GDBIntegrator.Config; + +public static class ErrorCode +{ + public const string LINE_STRING_IS_INVALID = "LINE_STRING_IS_INVALID"; + public const string LINE_STRING_IS_NOT_SIMPLE = "LINE_STRING_IS_NOT_SIMPLE"; + public const string LINE_STRING_IS_CLOSED = "LINE_STRING_IS_CLOSED"; + public const string LINE_STRING_ENDS_CLOSER_TO_EACH_OTHER_THAN_TOLERANCE = "LINE_STRING_ENDS_CLOSER_TO_EACH_OTHER_THAN_TOLERANCE"; + public const string LINE_STRING_ENDS_CLOSER_TO_THE_EDGE_THAN_TOLERANCE = "LINE_STRING_ENDS_CLOSER_TO_THE_EDGE_THAN_TOLERANCE"; + public const string ROUTE_SEGMENT_INTERSECTS_WITH_MULTIPLE_START_OR_END_ROUTE_NODES = "ROUTE_SEGMENT_INTERSECTS_WITH_MULTIPLE_START_OR_END_ROUTE_NODES"; + public const string RECEIVED_INVALID_MESSAGE = "RECEIVED_INVALID_MESSAGE"; + public const string UNKNOWN_ERROR = "UNKNOWN_ERROR"; + public const string ROUTE_NODE_INTERSECTS_WITH_ANOTHER_ROUTE_NODE = "ROUTE_NODE_INTERSECTS_WITH_ANOTHER_ROUTE_NODE"; + public const string ROUTE_NODE_CANNOT_MODIFY_GEOMETRY_AND_MARK_FOR_DELETION_IN_THE_SAME_OPERATION = "ROUTE_NODE_CANNOT_MODIFY_GEOMETRY_AND_MARK_FOR_DELETION_IN_THE_SAME_OPERATION"; + public const string USER_HAS_NOT_SELECTED_A_WORK_TASK = "USER_HAS_NOT_SELECTED_A_WORK_TASK"; + public const string MESSAGE_IS_MISSING_USERNAME = "MESSAGE_IS_MISSING_USERNAME"; + public const string ROUTE_NODE_GEOMETRY_UPDATE_NOT_ALLOWED_TO_INTERSECT_WITH_ROUTE_SEGMENT = "ROUTE_NODE_GEOMETRY_UPDATE_NOT_ALLOWED_TO_INTERSECT_WITH_ROUTE_SEGMENT"; + public const string ROUTE_NODE_INTERSECT_WITH_ROUTE_SEGMENT_CANNOT_BE_DELETED = "ROUTE_NODE_INTERSECT_WITH_ROUTE_SEGMENT_CANNOT_BE_DELETED"; + public const string CANNOT_DELETE_ROUTE_NODE_WITH_RELATED_EQUIPMENT = "CANNOT_DELETE_ROUTE_NODE_WITH_RELATED_EQUIPMENT"; + public const string CANNOT_DELETE_ROUTE_SEGMENT_WITH_RELATED_EQUIPMENT = "CANNOT_DELETE_ROUTE_SEGMENT_WITH_RELATED_EQUIPMENT"; + public const string ROUTE_NODE_MODIFIED_LESS_THAN_TOLERANCE = "ROUTE_NODE_MODIFIED_LESS_THAN_TOLERANCE"; +} diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Commands/CannotDeleteRouteNodeRelatedEquipmentException.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Commands/CannotDeleteRouteNodeRelatedEquipmentException.cs new file mode 100644 index 0000000..c5d0c30 --- /dev/null +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Commands/CannotDeleteRouteNodeRelatedEquipmentException.cs @@ -0,0 +1,20 @@ +using System; + +namespace OpenFTTH.GDBIntegrator.Integrator.Commands; + +public class CannotDeleteRouteNodeRelatedEquipmentException : Exception +{ + public CannotDeleteRouteNodeRelatedEquipmentException() + { + } + + public CannotDeleteRouteNodeRelatedEquipmentException(string message) + : base(message) + { + } + + public CannotDeleteRouteNodeRelatedEquipmentException(string message, Exception inner) + : base(message, inner) + { + } +} diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Commands/CannotDeleteRouteSegmentRelatedEquipmentException.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Commands/CannotDeleteRouteSegmentRelatedEquipmentException.cs new file mode 100644 index 0000000..2e9756c --- /dev/null +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Commands/CannotDeleteRouteSegmentRelatedEquipmentException.cs @@ -0,0 +1,20 @@ +using System; + +namespace OpenFTTH.GDBIntegrator.Integrator.Commands; + +public class CannotDeleteRouteSegmentRelatedEquipmentException : Exception +{ + public CannotDeleteRouteSegmentRelatedEquipmentException() + { + } + + public CannotDeleteRouteSegmentRelatedEquipmentException(string message) + : base(message) + { + } + + public CannotDeleteRouteSegmentRelatedEquipmentException(string message, Exception inner) + : base(message, inner) + { + } +} diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Commands/GeoDatabaseUpdated.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Commands/GeoDatabaseUpdated.cs index d01d23e..79bc31c 100644 --- a/src/OpenFTTH.GDBIntegrator.Integrator/Commands/GeoDatabaseUpdated.cs +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Commands/GeoDatabaseUpdated.cs @@ -1,6 +1,7 @@ using MediatR; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Newtonsoft.Json; using OpenFTTH.Events.RouteNetwork; using OpenFTTH.GDBIntegrator.Config; using OpenFTTH.GDBIntegrator.GeoDatabase; @@ -13,8 +14,10 @@ using OpenFTTH.GDBIntegrator.Producer; using OpenFTTH.GDBIntegrator.RouteNetwork; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; @@ -82,15 +85,17 @@ public GeoDatabaseUpdatedHandler( public async Task Handle(GeoDatabaseUpdated request, CancellationToken token) { - var eventId = request.UpdateMessage switch - { - RouteNodeMessage msg => msg.EventId, - RouteSegmentMessage msg => msg.EventId, - InvalidMessage msg => msg.EventId, - _ => throw new ArgumentException( - "Could not handle type of '{typeof(request.UpdateMessage)}'.") - }; + { + RouteNodeMessage msg => msg.EventId, + RouteSegmentMessage msg => msg.EventId, + InvalidMessage msg => msg.EventId, + // This is a very exceptional error and something is very wrong if this happens. + // It will keep retrying and crashing the service, which is intended. + // In case this happens it needs to be investigated manually. + _ => throw new ArgumentException( + "Could not handle type of '{typeof(request.UpdateMessage)}'.") + }; if (_eventIdStore.GetEventIds().Contains(eventId)) { @@ -101,7 +106,6 @@ public async Task Handle(GeoDatabaseUpdated request, CancellationToken tok try { _eventStore.Clear(); - await _geoDatabase.BeginTransaction(); if (request.UpdateMessage is RouteNodeMessage) @@ -122,9 +126,6 @@ await MarkToBeDeleted( "Message is invalid and we cannot rollback so we mark it to be deleted."); await _geoDatabase.Commit(); - // We send an updated event out, to notify that something has been rolled back to refresh GIS. - await SendGeographicalAreaUpdatedError(request); - return await Task.FromResult(new Unit()); } else @@ -135,9 +136,6 @@ await MarkToBeDeleted( await RollbackOrDelete((request.UpdateMessage as InvalidMessage).Message, "Message is invalid so we rollback or delete."); await _geoDatabase.Commit(); - // We send an updated event out, to notify that something has been rolled back to refresh GIS. - await SendGeographicalAreaUpdatedError(request); - return await Task.FromResult(new Unit()); } } @@ -145,21 +143,48 @@ await MarkToBeDeleted( if (_eventStore.Get().Count() > 0) { var username = GetUsername(request.UpdateMessage); + + // A username is always required. if (String.IsNullOrWhiteSpace(username)) { - throw new InvalidOperationException("The update message is missing username."); + await _geoDatabase.RollbackTransaction(); + await _geoDatabase.BeginTransaction(); + + await RollbackOrDelete( + request.UpdateMessage, + "The message is missing a username.", + ErrorCode.MESSAGE_IS_MISSING_USERNAME); + + await _geoDatabase.Commit(); + + return await Task.FromResult(new Unit()); } - var workTaskMrId = await GetUserWorkTaskMrId(username); + var workTask = await _workTaskService.GetUserWorkTask(username); + // A work task is always required. + if (workTask is null) + { + await _geoDatabase.RollbackTransaction(); + await _geoDatabase.BeginTransaction(); + + await RollbackOrDelete( + request.UpdateMessage, + "The user has not selected a work task.", + ErrorCode.USER_HAS_NOT_SELECTED_A_WORK_TASK + ); + + await _geoDatabase.Commit(); + + return await Task.FromResult(new Unit()); + } // We update the work task ids on the newly digitized network elements. - await UpdateWorkTaskIdOnNewlyDigitized(workTaskMrId); + await UpdateWorkTaskIdOnNewlyDigitized(workTask.Id); var editOperationOccuredEvent = CreateEditOperationOccuredEvent( - workTaskMrId, + workTask.Id, username, - eventId - ); + eventId); if (IsOperationEditEventValid(editOperationOccuredEvent)) { @@ -177,17 +202,48 @@ await MarkToBeDeleted( await _geoDatabase.Commit(); } } - catch (Exception e) + // This is not a pretty wasy to handle it, but it was the simplest way + // without having to restructure the whole thing. + catch (CannotDeleteRouteSegmentRelatedEquipmentException) { - _logger.LogError($"{e}: Rolling back geodatabase transactions."); await _geoDatabase.RollbackTransaction(); await _geoDatabase.BeginTransaction(); - await RollbackOrDelete(request.UpdateMessage, $"Rollback or delete because of exception: {e}"); + + await RollbackOrDelete( + request.UpdateMessage, + $"Cannot delete route segment when it has related equipment.", + ErrorCode.CANNOT_DELETE_ROUTE_SEGMENT_WITH_RELATED_EQUIPMENT + ); + await _geoDatabase.Commit(); - _logger.LogInformation($"{nameof(RouteNetworkEditOperationOccuredEvent)} is now rolled rollback."); + } + // This is not a pretty wasy to handle it, but it was the simplest way + // without having to restructure the whole thing. + catch (CannotDeleteRouteNodeRelatedEquipmentException) + { + await _geoDatabase.RollbackTransaction(); + await _geoDatabase.BeginTransaction(); - // We send an updated event out, to notify that something has been rolled back to refresh GIS. - await SendGeographicalAreaUpdatedError(request); + await RollbackOrDelete( + request.UpdateMessage, + $"Cannot delete route node when it has related equipment.", + ErrorCode.CANNOT_DELETE_ROUTE_NODE_WITH_RELATED_EQUIPMENT + ); + + await _geoDatabase.Commit(); + } + catch (Exception ex) + { + await _geoDatabase.RollbackTransaction(); + await _geoDatabase.BeginTransaction(); + + await RollbackOrDelete( + request.UpdateMessage, + $"Rollback or delete because of exception: {ex}", + ErrorCode.UNKNOWN_ERROR + ); + + await _geoDatabase.Commit(); } finally { @@ -221,58 +277,36 @@ await _mediator.Publish(new GeographicalAreaUpdated } // Use this function to send geographicalareaupdated when an error has occured, since we cannot use the modified geometries store. - private async Task SendGeographicalAreaUpdatedError(GeoDatabaseUpdated request) + private async Task SendUserErrorOccured( + GeoDatabaseUpdated request, + string errorCode) { try { + string username = null; if (request.UpdateMessage is RouteNodeMessage) { - var updateMessage = ((RouteNodeMessage)request.UpdateMessage); - RouteNode routeNode = null; - if (updateMessage.After.Coord is not null) - { - routeNode = updateMessage.After; - } - else if (updateMessage.Before.Coord is not null) - { - routeNode = updateMessage.Before; - } - - if (routeNode is not null) - { - await _mediator.Publish(new GeographicalAreaUpdated - { - RouteNodes = new List { routeNode }, - RouteSegment = new List(), - }); - } + username = ((RouteNodeMessage)request.UpdateMessage).After.Username; } else if (request.UpdateMessage is RouteSegmentMessage) { - var updateMessage = ((RouteSegmentMessage)request.UpdateMessage); - RouteSegment routeSegment = null; - if (updateMessage.After.Coord is not null) - { - routeSegment = updateMessage.After; - } - else if (updateMessage.Before.Coord is not null) - { - routeSegment = updateMessage.Before; - } - - if (routeSegment is not null) - { - await _mediator.Publish(new GeographicalAreaUpdated - { - RouteNodes = new List(), - RouteSegment = new List { routeSegment } - }); - } + username = ((RouteSegmentMessage)request.UpdateMessage).After.Username; } + + await _mediator.Publish( + new UserErrorOccurred( + errorCode: errorCode, + username: username + )).ConfigureAwait(false); } catch (Exception ex) { - _logger.LogError($"Could not send out {nameof(GeographicalAreaUpdated)} Exception: {ex}"); + // Just log it out, it is not dangerous if it is not send out, + // but should be fixed if it ever occurs, therefore we just log it. + _logger.LogError( + "Could not send out {MessageType} Exception: {Exception}", + nameof(UserErrorOccurred), + ex); } } @@ -310,7 +344,7 @@ private async Task MarkToBeDeleted(object message, string errorMessage) } } - private async Task RollbackOrDelete(object message, string errorMessage) + private async Task RollbackOrDelete(object message, string errorMessage, string errorCode = ErrorCode.UNKNOWN_ERROR) { if (message is RouteSegmentMessage) { @@ -321,24 +355,35 @@ private async Task RollbackOrDelete(object message, string errorMessage) if (rollbackSegment is not null) { - await _mediator.Publish(new RollbackInvalidRouteSegment(rollbackSegment, errorMessage)); + await _mediator.Publish( + new RollbackInvalidRouteSegment( + rollbackSegment, + errorMessage, + errorCode, + rollbackMessage.After.Username ?? "COULD_NOT_GET_USERNAME" + ) + ); } else { - await _mediator.Publish(new InvalidRouteSegmentOperation - { - RouteSegment = rollbackMessage.After, - Message = errorMessage - }); + await _mediator.Publish( + new InvalidRouteSegmentOperation( + routeSegment: rollbackMessage.After, + message: errorMessage, + errorCode: errorCode, + username: rollbackMessage.After.Username ?? "COULD_NOT_GET_USERNAME")); } } else { - await _mediator.Publish(new InvalidRouteSegmentOperation - { - RouteSegment = rollbackMessage.After, - Message = errorMessage - }); + await _mediator.Publish( + new InvalidRouteSegmentOperation( + routeSegment: rollbackMessage.After, + message: errorMessage, + errorCode: errorCode, + username: rollbackMessage.After.Username ?? "COULD_NOT_GET_USERNAME" + ) + ); } } else if (message is RouteNodeMessage) @@ -349,24 +394,36 @@ await _mediator.Publish(new InvalidRouteSegmentOperation var rollbackNode = await _geoDatabase.GetRouteNodeShadowTable(rollbackMessage.After.Mrid); if (rollbackNode is not null) { - await _mediator.Publish(new RollbackInvalidRouteNode(rollbackNode, errorMessage)); + await _mediator.Publish( + new RollbackInvalidRouteNode( + rollbackToNode: rollbackNode, + message: errorMessage, + errorCode: errorCode, + username: rollbackMessage.After.Username ?? "COULD_NOT_GET_USERNAME" + )); } else { - await _mediator.Publish(new InvalidRouteNodeOperation - { - RouteNode = rollbackMessage.After, - Message = errorMessage - }); + await _mediator.Publish( + new InvalidRouteNodeOperation( + routeNode: rollbackMessage.After, + message: errorMessage, + errorCode: errorCode, + username: rollbackMessage.After.Username ?? "COULD_NOT_GET_USERNAME" + ) + ); } } else { - await _mediator.Publish(new InvalidRouteNodeOperation - { - RouteNode = rollbackMessage.After, - Message = errorMessage - }); + await _mediator.Publish( + new InvalidRouteNodeOperation( + routeNode: rollbackMessage.After, + message: errorMessage, + errorCode: errorCode, + username: rollbackMessage.After.Username ?? "COULD_NOT_GET_USERNAME" + ) + ); } } else @@ -428,7 +485,8 @@ private async Task HandleRouteNode(RouteNodeMessage routeNodeMessage) var hasRelatedEquipment = await _validationService.HasRelatedEquipment(routeNodeMessage.After.Mrid); if (hasRelatedEquipment) { - throw new Exception("Cannot update route node since it has related equipment."); + throw new CannotDeleteRouteNodeRelatedEquipmentException( + "Cannot delete route node when it has releated equipment."); } } @@ -446,7 +504,14 @@ private async Task HandleRouteNode(RouteNodeMessage routeNodeMessage) } else { - await _mediator.Publish(new InvalidRouteNodeOperation { RouteNode = routeNodeMessage.After }); + await _mediator.Publish( + new InvalidRouteNodeOperation( + routeNode: routeNodeMessage.After, + message: $"Could not handle route node message. '{JsonConvert.SerializeObject(routeNodeMessage)}'", + errorCode: ErrorCode.UNKNOWN_ERROR, + username: routeNodeMessage.After.Username + ) + ); } } @@ -475,10 +540,13 @@ private async Task HandleRouteSegment(RouteSegmentMessage routeSegmentMessage) var possibleIllegalOperation = routeSegmentUpdatedEvents.Any(x => x.GetType() == typeof(RouteSegmentDeleted) || x.GetType() == typeof(RouteSegmentConnectivityChanged)); if (possibleIllegalOperation) { - var hasRelatedEquipment = await _validationService.HasRelatedEquipment(routeSegmentMessage.After.Mrid); + var hasRelatedEquipment = await _validationService.HasRelatedEquipment( + routeSegmentMessage.After.Mrid); + if (hasRelatedEquipment) { - throw new Exception("Cannot update route segment since it has related equipment."); + throw new CannotDeleteRouteSegmentRelatedEquipmentException( + "Cannot delete route segment when it has releated equipment."); } } @@ -496,7 +564,14 @@ private async Task HandleRouteSegment(RouteSegmentMessage routeSegmentMessage) } else { - await _mediator.Publish(new InvalidRouteSegmentOperation { RouteSegment = routeSegmentMessage.After }); + await _mediator.Publish( + new InvalidRouteSegmentOperation( + routeSegment: routeSegmentMessage.After, + message: "Could not figure out how to handle the route segment creation/update.", + errorCode: ErrorCode.UNKNOWN_ERROR, + username: routeSegmentMessage.After.Username + ) + ); } } @@ -520,17 +595,6 @@ private string GetUsername(object updateMessage) return username; } - private async Task GetUserWorkTaskMrId(string username) - { - var workTask = await _workTaskService.GetUserWorkTask(username); - if (workTask is null) - { - throw new ApplicationException($"User {username} does not have a selected work task."); - } - - return workTask.Id; - } - // Updates work task id on newly digitized routenetwork-elements // that do not yet have a WorkTaskMrid private async Task UpdateWorkTaskIdOnNewlyDigitized(Guid workTaskMrId) diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Factories/RouteNodeCommandFactory.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Factories/RouteNodeCommandFactory.cs index 1994bde..efbc2f6 100644 --- a/src/OpenFTTH.GDBIntegrator.Integrator/Factories/RouteNodeCommandFactory.cs +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Factories/RouteNodeCommandFactory.cs @@ -45,10 +45,57 @@ public async Task> CreateUpdatedEvent(RouteNode before, Rout throw new ArgumentException("Point is not valid."); if (IsModifiedDistanceLessThanTolerance(shadowTableNode, after)) - return new List { new RollbackInvalidRouteNode(shadowTableNode, "Modified distance less than tolerance.") }; + { + return new List + { + new RollbackInvalidRouteNode( + rollbackToNode: shadowTableNode, + message: "Route node's distance was modified less than tolerance.", + errorCode: ErrorCode.ROUTE_NODE_MODIFIED_LESS_THAN_TOLERANCE, + username: after.Username + ) + }; + } + + var intersectingRouteNodes = await _geoDatabase.GetIntersectingRouteNodes(after); + if (intersectingRouteNodes.Count > 0) + { + return new List + { + new RollbackInvalidRouteNode( + rollbackToNode: shadowTableNode, + message: "The route node intersects with another route node.", + errorCode: ErrorCode.ROUTE_NODE_INTERSECTS_WITH_ANOTHER_ROUTE_NODE, + username: after.Username + ) + }; + } + + if (IsMarkedToBeDeletedAndGeometryChanged(shadowTableNode, after)) + { + return new List + { + new RollbackInvalidRouteNode( + rollbackToNode: shadowTableNode, + message: "Modifying the geometry and marking the route node to be deleted in the same operation is not valid.", + errorCode: ErrorCode.ROUTE_NODE_CANNOT_MODIFY_GEOMETRY_AND_MARK_FOR_DELETION_IN_THE_SAME_OPERATION, + username: after.Username + ) + }; + } - if (!(await IsValidNodeUpdate(shadowTableNode, after))) - return new List { new RollbackInvalidRouteNode(shadowTableNode, "Is not a valid route node update.") }; + if (await BeforeValueIntersectsWithRouteSegmentAndAfterIsMarkedToBeDeleted(shadowTableNode, after)) + { + return new List + { + new RollbackInvalidRouteNode( + rollbackToNode: shadowTableNode, + message: "Route node that intersects with route segment cannot be marked to deleted.", + errorCode: ErrorCode.ROUTE_NODE_INTERSECT_WITH_ROUTE_SEGMENT_CANNOT_BE_DELETED, + username: after.Username + ) + }; + } await _geoDatabase.UpdateRouteNodeShadowTable(after); @@ -60,11 +107,23 @@ public async Task> CreateUpdatedEvent(RouteNode before, Rout var newIntersectingRouteSegments = intersectingRouteSegments .Where(x => !previousIntersectingRouteSegments.Any(y => y.Mrid == x.Mrid)).ToList(); if (newIntersectingRouteSegments.Count > 0) - return new List { new RollbackInvalidRouteNode(shadowTableNode, "Update to route node is invalid because it is insecting with route-segments.") }; + { + return new List + { + new RollbackInvalidRouteNode( + shadowTableNode, + "It is not allowed to change the geometry of a route node so it intersects with one or more route segments.", + errorCode: ErrorCode.ROUTE_NODE_GEOMETRY_UPDATE_NOT_ALLOWED_TO_INTERSECT_WITH_ROUTE_SEGMENT, + username: after.Username + ) + }; + } } if (after.MarkAsDeleted) + { return new List { new RouteNodeDeleted { RouteNode = after } }; + } return new List { new RouteNodeLocationChanged { RouteNodeAfter = after, RouteNodeBefore = shadowTableNode } }; } @@ -72,12 +131,17 @@ public async Task> CreateUpdatedEvent(RouteNode before, Rout public async Task> CreateDigitizedEvent(RouteNode routeNode) { if (routeNode is null) - throw new ArgumentNullException($"Parameter {nameof(routeNode)} cannot be null"); + { + throw new ArgumentNullException($"Parameter {nameof(routeNode)} cannot be null."); + } // If we find the route node is in the shadow table, it means that it was created by the application and we therefore do nothing. if (await _geoDatabase.RouteNodeInShadowTableExists(routeNode.Mrid)) { - return new List { new DoNothing($"{nameof(RouteNode)} with id: '{routeNode.Mrid}' was created by {routeNode.ApplicationName} therefore do nothing.") }; + return new List + { + new DoNothing($"{nameof(RouteNode)} with id: '{routeNode.Mrid}' was created by {routeNode.ApplicationName} therefore do nothing.") + }; } await _geoDatabase.InsertRouteNodeShadowTable(routeNode); @@ -87,11 +151,21 @@ public async Task> CreateDigitizedEvent(RouteNode routeNode) if (intersectingRouteNodes.Count > 0) { - return new List { new InvalidRouteNodeOperation { RouteNode = routeNode, Message = "RouteNode intersects with another RouteNode" } }; + return new List + { + new InvalidRouteNodeOperation( + routeNode: routeNode, + message: "The route node intersects with another route node.", + errorCode: ErrorCode.ROUTE_NODE_INTERSECTS_WITH_ANOTHER_ROUTE_NODE, + username: routeNode.Username + ) + }; } if (intersectingRouteSegments.Count == 0) + { return new List { new NewRouteNodeDigitized { RouteNode = routeNode } }; + } if (intersectingRouteSegments.Count == 1) { @@ -106,19 +180,27 @@ public async Task> CreateDigitizedEvent(RouteNode routeNode) return notifications; } - return new List { new InvalidRouteNodeOperation { RouteNode = routeNode, Message = "Route node did not fit any condition in command factory." } }; + return new List + { + new InvalidRouteNodeOperation( + routeNode: routeNode, + message: "Route node did not fit any condition in command factory.", + errorCode: ErrorCode.UNKNOWN_ERROR, + username: routeNode.Username + ) + }; + } + + private bool IsMarkedToBeDeletedAndGeometryChanged(RouteNode shadowTableNode, RouteNode after) + { + return after.MarkAsDeleted && !after.GetPoint().EqualsTopologically(shadowTableNode.GetPoint()); } - private async Task IsValidNodeUpdate(RouteNode shadowTableNode, RouteNode after) + private async Task BeforeValueIntersectsWithRouteSegmentAndAfterIsMarkedToBeDeleted(RouteNode shadowTableNode, RouteNode after) { var startRouteSegment = await _geoDatabase.GetIntersectingStartRouteSegments(shadowTableNode); var endRouteSegment = await _geoDatabase.GetIntersectingEndRouteSegments(shadowTableNode); - var intersectingRouteNodes = await _geoDatabase.GetIntersectingRouteNodes(after); - - if (((startRouteSegment.Count + endRouteSegment.Count) > 0 && after.MarkAsDeleted) || intersectingRouteNodes.Count > 0) - return false; - - return true; + return (startRouteSegment.Count + endRouteSegment.Count) > 0 && after.MarkAsDeleted; } private bool AlreadyUpdated(RouteNode routeNode, RouteNode routeNodeShadowTable) diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Factories/RouteSegmentCommandFactory.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Factories/RouteSegmentCommandFactory.cs index e8e14b4..0701251 100644 --- a/src/OpenFTTH.GDBIntegrator.Integrator/Factories/RouteSegmentCommandFactory.cs +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Factories/RouteSegmentCommandFactory.cs @@ -1,16 +1,16 @@ +using MediatR; +using Microsoft.Extensions.Options; +using NetTopologySuite.Geometries; +using OpenFTTH.GDBIntegrator.Config; +using OpenFTTH.GDBIntegrator.GeoDatabase; +using OpenFTTH.GDBIntegrator.Integrator.Notifications; +using OpenFTTH.GDBIntegrator.RouteNetwork; +using OpenFTTH.GDBIntegrator.RouteNetwork.Factories; +using OpenFTTH.GDBIntegrator.RouteNetwork.Validators; using System; -using System.Threading.Tasks; using System.Collections.Generic; using System.Linq; -using OpenFTTH.GDBIntegrator.RouteNetwork; -using OpenFTTH.GDBIntegrator.RouteNetwork.Validators; -using OpenFTTH.GDBIntegrator.RouteNetwork.Factories; -using OpenFTTH.GDBIntegrator.Integrator.Notifications; -using OpenFTTH.GDBIntegrator.Config; -using OpenFTTH.GDBIntegrator.GeoDatabase; -using Microsoft.Extensions.Options; -using MediatR; -using NetTopologySuite.Geometries; +using System.Threading.Tasks; namespace OpenFTTH.GDBIntegrator.Integrator.Factories { @@ -43,8 +43,18 @@ public async Task> CreateUpdatedEvent(RouteSegment be if (AlreadyUpdated(after, routeSegmentShadowTableBeforeUpdate)) return new List { new DoNothing($"{nameof(RouteSegment)} is already updated, therefore do nothing.") }; - if (!_routeSegmentValidator.LineIsValid(after.GetLineString())) - throw new Exception("Linestring is not valid."); + var (isValid, isValidErrorCode) = _routeSegmentValidator.LineIsValid(after.GetLineString()); + if (!isValid) + { + return new List + { + new RollbackInvalidRouteSegment( + rollbackToSegment: routeSegmentShadowTableBeforeUpdate, + message: $"The line string is invalid '{after.GetGeoJsonCoordinate()}'.", + errorCode: isValidErrorCode, + username: after.Username) + }; + } await _geoDatabase.UpdateRouteSegmentShadowTable(after); @@ -116,8 +126,19 @@ public async Task> CreateDigitizedEvent(RouteSegment // Update integrator "shadow table" with the used digitized segment await _geoDatabase.InsertRouteSegmentShadowTable(routeSegment); - if (!_routeSegmentValidator.LineIsValid(routeSegment.GetLineString())) - return new List { new InvalidRouteSegmentOperation { RouteSegment = routeSegment } }; + var (isValid, isValidErrorCode) = _routeSegmentValidator.LineIsValid(routeSegment.GetLineString()); + if (!isValid) + { + return new List + { + new InvalidRouteSegmentOperation( + routeSegment: routeSegment, + message: $"The line string is invalid '{routeSegment.GetGeoJsonCoordinate()}'.", + errorCode: isValidErrorCode, + username: routeSegment.Username + ) + }; + } var intersectingStartNodes = await _geoDatabase.GetIntersectingStartRouteNodes(routeSegment); var intersectingEndNodes = await _geoDatabase.GetIntersectingEndRouteNodes(routeSegment); @@ -126,7 +147,17 @@ public async Task> CreateDigitizedEvent(RouteSegment var allIntersectingRouteNodesNoEdges = await _geoDatabase.GetAllIntersectingRouteNodesNotIncludingEdges(routeSegment); if (intersectingStartNodes.Count >= 2 || intersectingEndNodes.Count >= 2) - return new List { new InvalidRouteSegmentOperation { RouteSegment = routeSegment } }; + { + return new List + { + new InvalidRouteSegmentOperation( + routeSegment: routeSegment, + message: "The line string intersects with two or more start or end nodes.", + errorCode: ErrorCode.ROUTE_SEGMENT_INTERSECTS_WITH_MULTIPLE_START_OR_END_ROUTE_NODES, + username: routeSegment.Username + ) + }; + } var notifications = new List(); diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/InvalidRouteNodeOperation.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/InvalidRouteNodeOperation.cs index a2b7e9b..84ef1a9 100644 --- a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/InvalidRouteNodeOperation.cs +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/InvalidRouteNodeOperation.cs @@ -1,33 +1,75 @@ -using OpenFTTH.GDBIntegrator.RouteNetwork; -using OpenFTTH.GDBIntegrator.GeoDatabase; using MediatR; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using OpenFTTH.GDBIntegrator.GeoDatabase; +using OpenFTTH.GDBIntegrator.Producer; +using OpenFTTH.GDBIntegrator.RouteNetwork; +using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; namespace OpenFTTH.GDBIntegrator.Integrator.Notifications { public class InvalidRouteNodeOperation : INotification { - public RouteNode RouteNode { get; set; } - public string Message { get; set; } + public RouteNode RouteNode { get; private set; } + public string Message { get; private set; } + public string ErrorCode { get; private set; } + public string Username { get; private set; } + + public InvalidRouteNodeOperation( + RouteNode routeNode, + string message, + string errorCode, + string username) + { + RouteNode = routeNode; + Message = message; + ErrorCode = errorCode; + Username = username; + } } public class InvalidRouteNodeOperationHandler : INotificationHandler { private readonly IGeoDatabase _geoDatabase; private readonly ILogger _logger; + private readonly INotificationClient _notificationClient; - public InvalidRouteNodeOperationHandler(IGeoDatabase geoDatabase, ILogger logger) + public InvalidRouteNodeOperationHandler( + IGeoDatabase geoDatabase, + ILogger logger, + INotificationClient notificationClient) { _geoDatabase = geoDatabase; _logger = logger; + _notificationClient = notificationClient; } public async Task Handle(InvalidRouteNodeOperation request, CancellationToken token) { _logger.LogWarning($"Deleteting {nameof(RouteNode)} with mrid '{request.RouteNode.Mrid}'. Because: {request.Message}"); await _geoDatabase.DeleteRouteNode(request.RouteNode.Mrid); + + try + { + var userErrorOccurred = new UserErrorOccurred( + request.ErrorCode, + request.Username); + + _notificationClient.Notify( + "UserErrorOccurred", + JsonConvert.SerializeObject(userErrorOccurred)); + } + catch (Exception ex) + { + // In case something goes wrong here, we just want to log it and do nothing else. + // The worst thing that happens is that the user is not notified by the error, + // but it's better than everything retrying again. + _logger.LogWarning( + "Failed to send user error occurred notification, {Exception}", + ex); + } } } } diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/InvalidRouteSegmentOperation.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/InvalidRouteSegmentOperation.cs index 5e49ec1..a0163ea 100644 --- a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/InvalidRouteSegmentOperation.cs +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/InvalidRouteSegmentOperation.cs @@ -1,9 +1,12 @@ -using OpenFTTH.GDBIntegrator.RouteNetwork; -using OpenFTTH.GDBIntegrator.GeoDatabase; using MediatR; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using OpenFTTH.GDBIntegrator.GeoDatabase; +using OpenFTTH.GDBIntegrator.Producer; +using OpenFTTH.GDBIntegrator.RouteNetwork; +using System; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; namespace OpenFTTH.GDBIntegrator.Integrator.Notifications { @@ -11,23 +14,62 @@ public class InvalidRouteSegmentOperation : INotification { public RouteSegment RouteSegment { get; set; } public string Message { get; set; } + public string ErrorCode { get; init; } + public string Username { get; init; } + + public InvalidRouteSegmentOperation( + RouteSegment routeSegment, + string message, + string errorCode, + string username) + { + RouteSegment = routeSegment; + Message = message; + ErrorCode = errorCode; + Username = username; + } } public class InvalidRouteSegmentOperationHandler : INotificationHandler { private readonly IGeoDatabase _geoDatabase; private readonly ILogger _logger; + private readonly INotificationClient _notificationClient; - public InvalidRouteSegmentOperationHandler(IGeoDatabase geoDatabase, ILogger logger) + public InvalidRouteSegmentOperationHandler( + IGeoDatabase geoDatabase, + ILogger logger, + INotificationClient notificationClient) { _geoDatabase = geoDatabase; _logger = logger; + _notificationClient = notificationClient; } public async Task Handle(InvalidRouteSegmentOperation request, CancellationToken token) { _logger.LogWarning($"Deleteting {nameof(RouteSegment)} with mrid '{request.RouteSegment.Mrid}' - Because: {request.Message}"); await _geoDatabase.DeleteRouteSegment(request.RouteSegment.Mrid); + + try + { + var userErrorOccurred = new UserErrorOccurred( + request.ErrorCode, + request.Username); + + _notificationClient.Notify( + "UserErrorOccurred", + JsonConvert.SerializeObject(userErrorOccurred)); + } + catch (Exception ex) + { + // In case something goes wrong here, we just want to log it and do nothing else. + // The worst thing that happens is that the user is not notified by the error, + // but it's better than everything retrying again. + _logger.LogWarning( + "Failed to send user error occurred notification, {Exception}", + ex); + } } } } diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RollbackInvalidRouteNode.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RollbackInvalidRouteNode.cs index 46fe42b..05d833a 100644 --- a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RollbackInvalidRouteNode.cs +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RollbackInvalidRouteNode.cs @@ -1,26 +1,32 @@ using MediatR; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using OpenFTTH.GDBIntegrator.GeoDatabase; +using OpenFTTH.GDBIntegrator.Producer; using OpenFTTH.GDBIntegrator.RouteNetwork; +using System; +using System.Threading; +using System.Threading.Tasks; namespace OpenFTTH.GDBIntegrator.Integrator.Notifications { public class RollbackInvalidRouteNode : INotification { - public RouteNode RollbackToNode { get; } - public string Message { get; } - - public RollbackInvalidRouteNode(RouteNode rollbackToNode) - { - RollbackToNode = rollbackToNode; - } + public RouteNode RollbackToNode { get; private set; } + public string Message { get; private set; } + public string ErrorCode { get; private set; } + public string Username { get; private set; } - public RollbackInvalidRouteNode(RouteNode rollbackToNode, string message) + public RollbackInvalidRouteNode( + RouteNode rollbackToNode, + string message, + string errorCode, + string username) { RollbackToNode = rollbackToNode; Message = message; + ErrorCode = errorCode; + Username = username; } } @@ -28,17 +34,42 @@ public class RollbackInvalidRouteNodeHandler : INotificationHandler _logger; private readonly IGeoDatabase _geoDatabase; + private readonly INotificationClient _notificationClient; - public RollbackInvalidRouteNodeHandler(ILogger logger, IGeoDatabase geoDatabase) + public RollbackInvalidRouteNodeHandler( + ILogger logger, + IGeoDatabase geoDatabase, + INotificationClient notificationClient) { _logger = logger; _geoDatabase = geoDatabase; + _notificationClient = notificationClient; } public async Task Handle(RollbackInvalidRouteNode request, CancellationToken token) { _logger.LogWarning($"Rollbacks invalid {nameof(RouteNode)} with id: '{request.RollbackToNode.Mrid}'. {request.Message}"); await _geoDatabase.UpdateRouteNode(request.RollbackToNode); + + try + { + var userErrorOccurred = new UserErrorOccurred( + request.ErrorCode, + request.Username); + + _notificationClient.Notify( + "UserErrorOccurred", + JsonConvert.SerializeObject(userErrorOccurred)); + } + catch (Exception ex) + { + // In case something goes wrong here, we just want to log it and do nothing else. + // The worst thing that happens is that the user is not notified by the error, + // but it's better than everything retrying again. + _logger.LogWarning( + "Failed to send user error occurred notification, {Exception}.", + ex); + } } } } diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RollbackInvalidRouteSegment.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RollbackInvalidRouteSegment.cs index 16c684a..3fc585f 100644 --- a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RollbackInvalidRouteSegment.cs +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RollbackInvalidRouteSegment.cs @@ -1,26 +1,32 @@ using MediatR; -using System.Threading; -using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; using OpenFTTH.GDBIntegrator.GeoDatabase; +using OpenFTTH.GDBIntegrator.Producer; using OpenFTTH.GDBIntegrator.RouteNetwork; +using System; +using System.Threading; +using System.Threading.Tasks; namespace OpenFTTH.GDBIntegrator.Integrator.Notifications { public class RollbackInvalidRouteSegment : INotification { - public RouteSegment RollbackToSegment { get; } - public string Message { get; } + public RouteSegment RollbackToSegment { get; private set; } + public string Message { get; private set; } + public string ErrorCode { get; private set; } + public string Username { get; private set; } - public RollbackInvalidRouteSegment(RouteSegment rollbackToSegment) - { - RollbackToSegment = rollbackToSegment; - } - - public RollbackInvalidRouteSegment(RouteSegment rollbackToSegment, string message) + public RollbackInvalidRouteSegment( + RouteSegment rollbackToSegment, + string message, + string errorCode, + string username) { RollbackToSegment = rollbackToSegment; Message = message; + ErrorCode = errorCode; + Username = username; } } @@ -28,17 +34,43 @@ public class RollbackInvalidRouteSegmentHandler : INotificationHandler _logger; private readonly IGeoDatabase _geoDatabase; + private readonly INotificationClient _notificationClient; - public RollbackInvalidRouteSegmentHandler(ILogger logger, IGeoDatabase geoDatabase) + public RollbackInvalidRouteSegmentHandler( + ILogger logger, + IGeoDatabase geoDatabase, + INotificationClient notificationClient) { _logger = logger; _geoDatabase = geoDatabase; + _notificationClient = notificationClient; } public async Task Handle(RollbackInvalidRouteSegment request, CancellationToken token) { - _logger.LogWarning($"Rollbacks invalid {nameof(RouteSegment)} with id: '{request.RollbackToSegment.Mrid}'. {request.Message}"); + _logger.LogWarning($"Rollbacks invalid {nameof(RouteSegment)} with id: '{request.RollbackToSegment.Mrid}'. {request.Message ?? request.ErrorCode}"); + await _geoDatabase.UpdateRouteSegment(request.RollbackToSegment); + + try + { + var userErrorOccurred = new UserErrorOccurred( + request.ErrorCode, + request.Username); + + _notificationClient.Notify( + "UserErrorOccurred", + JsonConvert.SerializeObject(userErrorOccurred)); + } + catch (Exception ex) + { + // In case something goes wrong here, we just want to log it and do nothing else. + // The worst thing that happens is that the user is not notified by the error, + // but it's better than everything retrying again. + _logger.LogWarning( + "Failed to send user error occurred notification, {Exception}.", + ex); + } } } } diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RouteNodeLocationChanged.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RouteNodeLocationChanged.cs index 60cf6e0..1059c9b 100644 --- a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RouteNodeLocationChanged.cs +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/RouteNodeLocationChanged.cs @@ -94,7 +94,7 @@ public async Task Handle(RouteNodeLocationChanged request, CancellationToken tok throw new InvalidOperationException("Route segments intersects with any route nodes"); } - var anyRouteSegmentInvalid = routeSegmentsToBeUpdated.Any(x => !_routeSegmentValidator.LineIsValid(x.GetLineString())); + var anyRouteSegmentInvalid = routeSegmentsToBeUpdated.Any(x => !_routeSegmentValidator.LineIsValid(x.GetLineString()).isValid); if (anyRouteSegmentInvalid) { throw new InvalidOperationException("Route node move results in invalid routesegment geometries."); diff --git a/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/UserErrorOccured.cs b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/UserErrorOccured.cs new file mode 100644 index 0000000..7a5150c --- /dev/null +++ b/src/OpenFTTH.GDBIntegrator.Integrator/Notifications/UserErrorOccured.cs @@ -0,0 +1,47 @@ +using MediatR; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using OpenFTTH.GDBIntegrator.Producer; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenFTTH.GDBIntegrator.Integrator.Notifications; + +public class UserErrorOccurred : INotification +{ + public string ErrorCode { get; init; } + public string Username { get; init; } + + public UserErrorOccurred( + string errorCode, + string username) + { + ErrorCode = errorCode; + Username = username; + } +} + +public class UserErrorOccurredHandler : INotificationHandler +{ + private readonly ILogger _logger; + private readonly INotificationClient _notificationClient; + + public UserErrorOccurredHandler( + ILogger logger, + INotificationClient notificationClient) + { + _logger = logger; + _notificationClient = notificationClient; + } + + public Task Handle(UserErrorOccurred request, CancellationToken token) + { + _logger.LogDebug($"Starting {nameof(UserErrorOccurredHandler)}."); + + _notificationClient.Notify( + "UserErrorOccurred", + JsonConvert.SerializeObject(request)); + + return Task.CompletedTask; + } +} diff --git a/src/OpenFTTH.GDBIntegrator.RouteNetwork/Validators/IRouteSegmentValidator.cs b/src/OpenFTTH.GDBIntegrator.RouteNetwork/Validators/IRouteSegmentValidator.cs index d50c274..4d0435c 100644 --- a/src/OpenFTTH.GDBIntegrator.RouteNetwork/Validators/IRouteSegmentValidator.cs +++ b/src/OpenFTTH.GDBIntegrator.RouteNetwork/Validators/IRouteSegmentValidator.cs @@ -8,6 +8,6 @@ public interface IRouteSegmentValidator /// Logic that checks if a line drawn by user (representing a route segment) is ok to be futher processed /// /// bool - bool LineIsValid(LineString lineString); + (bool isValid, string errorCode) LineIsValid(LineString lineString); } } diff --git a/src/OpenFTTH.GDBIntegrator.RouteNetwork/Validators/RouteSegmentValidator.cs b/src/OpenFTTH.GDBIntegrator.RouteNetwork/Validators/RouteSegmentValidator.cs index a11f866..ec32865 100644 --- a/src/OpenFTTH.GDBIntegrator.RouteNetwork/Validators/RouteSegmentValidator.cs +++ b/src/OpenFTTH.GDBIntegrator.RouteNetwork/Validators/RouteSegmentValidator.cs @@ -19,43 +19,43 @@ public RouteSegmentValidator( _applicationSettings = applicationSettings.Value; } - public bool LineIsValid(LineString lineString) + public (bool isValid, string errorCode) LineIsValid(LineString lineString) { if (!lineString.IsValid) { LogValidationError("IsValid", lineString); - return false; + return (false, ErrorCode.LINE_STRING_IS_INVALID); } // We don't want lines that are not simple - i.e. self intersecting if (!lineString.IsSimple) { LogValidationError("IsSimple", lineString); - return false; + return (false, ErrorCode.LINE_STRING_IS_NOT_SIMPLE); } // We don't want lines that are closes - i.e. where the ends of the line is snapped together if (lineString.IsClosed) { LogValidationError("IsClosed", lineString); - return false; + return (false, ErrorCode.LINE_STRING_IS_CLOSED); } // We don't want ends closer to each other than tolerance if (lineString.StartPoint.Distance(lineString.EndPoint) < _applicationSettings.Tolerance) { LogValidationError("EndsCloserToEachOtherThanTolereance", lineString); - return false; + return (false, ErrorCode.LINE_STRING_ENDS_CLOSER_TO_EACH_OTHER_THAN_TOLERANCE); } // We don't want ends closer to the edge than tolerance if (!GeometrySnapper.SnapToSelf(lineString, _applicationSettings.Tolerance, false).Equals(lineString)) { LogValidationError("EndsCloserToTheEdgeThanTolereance", lineString); - return false; + return (false, ErrorCode.LINE_STRING_ENDS_CLOSER_TO_THE_EDGE_THAN_TOLERANCE); } - return true; + return (true, null); } private void LogValidationError(string errorName, LineString lineString) diff --git a/test/OpenFTTH.GDBIntegrator.Integrator.Tests/Factories/RouteNodeCommandFactoryTest.cs b/test/OpenFTTH.GDBIntegrator.Integrator.Tests/Factories/RouteNodeCommandFactoryTest.cs index eca6ce1..3d8d114 100644 --- a/test/OpenFTTH.GDBIntegrator.Integrator.Tests/Factories/RouteNodeCommandFactoryTest.cs +++ b/test/OpenFTTH.GDBIntegrator.Integrator.Tests/Factories/RouteNodeCommandFactoryTest.cs @@ -153,7 +153,7 @@ public async Task CreateDigitizedEvent_ShouldReturnInvalidRouteNodeOperation_OnI } [Fact] - public async Task CreateUpdatedEvent_ShouldReturnRouteNodeDeletedEvent_OnRouteNodeMarkAsDeletedSetAndNoIntersectingSegments() + public async Task CreateUpdatedEvent_ShouldReturnRouteNodeDeletedEvent_OnRouteNodeMarkAsDeletedSetAndNoIntersectingSegmentsAndGeometryNotModified() { var applicationSetting = A.Fake>(); var geoDatabase = A.Fake(); @@ -161,22 +161,22 @@ public async Task CreateUpdatedEvent_ShouldReturnRouteNodeDeletedEvent_OnRouteNo var afterNode = A.Fake(); var shadowTableRouteNode = A.Fake(); var appSettings = new ApplicationSetting { Tolerance = 0.01 }; + var shadowTablePoint = CreatePoint(565931.4446905176, 6197297.75114815); + var afterPoint = CreatePoint(565931.4446905176, 6197297.75114815); var mrid = Guid.NewGuid(); A.CallTo(() => beforeNode.MarkAsDeleted).Returns(false); A.CallTo(() => beforeNode.Mrid).Returns(mrid); A.CallTo(() => afterNode.MarkAsDeleted).Returns(true); A.CallTo(() => afterNode.Mrid).Returns(mrid); - A.CallTo(() => afterNode.GetPoint()).Returns(CreatePoint(565931.4446905176, 6197297.75114815)); - A.CallTo(() => shadowTableRouteNode.GetPoint()).Returns(CreatePoint(565930.4446905176, 6197297.75114815)); + A.CallTo(() => afterNode.GetPoint()).Returns(afterPoint); + A.CallTo(() => shadowTableRouteNode.GetPoint()).Returns(shadowTablePoint); A.CallTo(() => applicationSetting.Value).Returns(appSettings); A.CallTo(() => geoDatabase.GetRouteNodeShadowTable(afterNode.Mrid, false)).Returns(shadowTableRouteNode); A.CallTo(() => geoDatabase.GetIntersectingRouteSegments(afterNode)).Returns(new List { }); - var point = A.Fake(); var routeNodeValidator = A.Fake(); - A.CallTo(() => afterNode.GetPoint()).Returns(point); - A.CallTo(() => routeNodeValidator.PointIsValid(point)).Returns(true); + A.CallTo(() => routeNodeValidator.PointIsValid(afterPoint)).Returns(true); var factory = new RouteNodeCommandFactory(applicationSetting, geoDatabase, routeNodeValidator); var result = (RouteNodeDeleted)(await factory.CreateUpdatedEvent(beforeNode, afterNode)).First(); @@ -187,6 +187,50 @@ public async Task CreateUpdatedEvent_ShouldReturnRouteNodeDeletedEvent_OnRouteNo } } + [Fact] + public async Task CreateUpdatedEvent_ShouldReturnRollbackInvalidRouteNodeOperation_WhenGeometryIsChangedAndMarkedToBeDeletedInSameOperation() + { + var applicationSetting = A.Fake>(); + var geoDatabase = A.Fake(); + var shadowTableNode = A.Fake(); + var beforeNode = A.Fake(); + var afterNode = A.Fake(); + var validationService = A.Fake(); + var username = "myAwesomeUsername"; + afterNode.Username = username; + var shadowPoint = CreatePoint(565931.4446905176, 6197297.75114815); + var afterPoint = CreatePoint(665931.4446905176, 7197297.75114815); + + A.CallTo(() => afterNode.Mrid).Returns(Guid.NewGuid()); + A.CallTo(() => afterNode.MarkAsDeleted).Returns(true); + A.CallTo(() => shadowTableNode.Mrid).Returns(Guid.NewGuid()); + A.CallTo(() => validationService.HasRelatedEquipment(afterNode.Mrid)).Returns(false); + A.CallTo(() => geoDatabase.GetRouteNodeShadowTable(afterNode.Mrid, false)).Returns(shadowTableNode); + A.CallTo(() => geoDatabase.GetIntersectingStartRouteSegments(shadowTableNode)).Returns(new List()); + A.CallTo(() => geoDatabase.GetIntersectingEndRouteSegments(shadowTableNode)).Returns(new List()); + A.CallTo(() => afterNode.GetPoint()).Returns(afterPoint); + A.CallTo(() => shadowTableNode.GetPoint()).Returns(shadowPoint); + + var routeNodeValidator = A.Fake(); + A.CallTo(() => routeNodeValidator.PointIsValid(afterPoint)).Returns(true); + + var factory = new RouteNodeCommandFactory(applicationSetting, geoDatabase, routeNodeValidator); + var result = await factory.CreateUpdatedEvent(beforeNode, afterNode); + + var expected = new RollbackInvalidRouteNode( + rollbackToNode: shadowTableNode, + message: "Modifying the geometry and marking the route node to be deleted in the same operation is not valid.", + errorCode: "ROUTE_NODE_CANNOT_MODIFY_GEOMETRY_AND_MARK_FOR_DELETION_IN_THE_SAME_OPERATION", + username: username + ); + + using (var scope = new AssertionScope()) + { + result.Should().HaveCount(1); + result[0].Should().BeOfType(typeof(RollbackInvalidRouteNode)).And.BeEquivalentTo(expected); + } + } + [Fact] public async Task CreateUpdatedEvent_ShouldReturnRollbackInvalidRouteNodeOperation_OnRouteNodeMarkAsDeletedSetAndInsectsWithAnyRouteSegments() { @@ -196,26 +240,36 @@ public async Task CreateUpdatedEvent_ShouldReturnRollbackInvalidRouteNodeOperati var beforeNode = A.Fake(); var afterNode = A.Fake(); var validationService = A.Fake(); + var username = "myAwesomeUsername"; + afterNode.Username = username; + var shadowPoint = CreatePoint(565931.4446905176, 6197297.75114815); + var afterPoint = CreatePoint(565931.4446905176, 6197297.75114815); A.CallTo(() => afterNode.Mrid).Returns(Guid.NewGuid()); A.CallTo(() => afterNode.MarkAsDeleted).Returns(true); A.CallTo(() => shadowTableNode.Mrid).Returns(Guid.NewGuid()); - A.CallTo(() => validationService.HasRelatedEquipment(afterNode.Mrid)).Returns(true); + A.CallTo(() => validationService.HasRelatedEquipment(afterNode.Mrid)).Returns(false); A.CallTo(() => geoDatabase.GetRouteNodeShadowTable(afterNode.Mrid, false)).Returns(shadowTableNode); A.CallTo(() => geoDatabase.GetIntersectingStartRouteSegments(shadowTableNode)).Returns(new List { A.Fake() }); A.CallTo(() => geoDatabase.GetIntersectingEndRouteSegments(shadowTableNode)).Returns(new List { A.Fake() }); - A.CallTo(() => afterNode.GetPoint()).Returns(CreatePoint(665931.4446905176, 7197297.75114815)); - A.CallTo(() => shadowTableNode.GetPoint()).Returns(CreatePoint(565931.4446905176, 6197297.75114815)); + A.CallTo(() => afterNode.GetPoint()).Returns(afterPoint); + A.CallTo(() => shadowTableNode.GetPoint()).Returns(shadowPoint); + + Console.WriteLine("Got in here1"); - var point = A.Fake(); var routeNodeValidator = A.Fake(); - A.CallTo(() => afterNode.GetPoint()).Returns(point); - A.CallTo(() => routeNodeValidator.PointIsValid(point)).Returns(true); + A.CallTo(() => routeNodeValidator.PointIsValid(afterPoint)).Returns(true); var factory = new RouteNodeCommandFactory(applicationSetting, geoDatabase, routeNodeValidator); var result = await factory.CreateUpdatedEvent(beforeNode, afterNode); - var expected = new RollbackInvalidRouteNode(shadowTableNode, "Is not a valid route node update."); + var expected = new RollbackInvalidRouteNode( + rollbackToNode: shadowTableNode, + message: "Route node that intersects with route segment cannot be marked to deleted.", + errorCode: "ROUTE_NODE_INTERSECT_WITH_ROUTE_SEGMENT_CANNOT_BE_DELETED", + username: username + ); + using (var scope = new AssertionScope()) { result.Should().HaveCount(1); @@ -232,6 +286,8 @@ public async Task CreateUpdatedEvent_ShouldReturnRollbackInvalidRouteNodeOperati var afterNode = A.Fake(); var shadowTableRouteNode = A.Fake(); var appSettings = new ApplicationSetting { Tolerance = 0.01 }; + var username = "myAwesomeUsername"; + afterNode.Username = username; A.CallTo(() => applicationSetting.Value).Returns(appSettings); A.CallTo(() => afterNode.Mrid).Returns(Guid.NewGuid()); @@ -251,7 +307,12 @@ public async Task CreateUpdatedEvent_ShouldReturnRollbackInvalidRouteNodeOperati var factory = new RouteNodeCommandFactory(applicationSetting, geoDatabase, routeNodeValidator); var result = (await factory.CreateUpdatedEvent(beforeNode, afterNode)).First(); - var expected = new RollbackInvalidRouteNode(beforeNode, "Is not a valid route node update."); + var expected = new RollbackInvalidRouteNode( + rollbackToNode: beforeNode, + message: "The route node intersects with another route node.", + errorCode: "ROUTE_NODE_INTERSECTS_WITH_ANOTHER_ROUTE_NODE", + username: username + ); result.Should().BeOfType(typeof(RollbackInvalidRouteNode)).And.BeEquivalentTo(expected); } @@ -300,6 +361,7 @@ public async Task CreateUpdatedEvent_ShouldReturnRollbackInvalidRouteNode_OnIsMo var shadowTableRouteNode = A.Fake(); var appSettings = new ApplicationSetting { Tolerance = 0.01 }; var point = CreatePoint(552428.7508312801, 6188868.185819111); + var username = "myAwesomeUsername"; A.CallTo(() => applicationSetting.Value).Returns(appSettings); A.CallTo(() => afterNode.Mrid).Returns(Guid.NewGuid()); @@ -317,7 +379,12 @@ public async Task CreateUpdatedEvent_ShouldReturnRollbackInvalidRouteNode_OnIsMo var result = (await factory.CreateUpdatedEvent(beforeNode, afterNode)).First(); - var expected = new RollbackInvalidRouteNode(shadowTableRouteNode, "Modified distance less than tolerance."); + var expected = new RollbackInvalidRouteNode( + rollbackToNode: shadowTableRouteNode, + message: "Modified distance less than tolerance.", + errorCode: "", + username: username + ); result.Should().BeOfType(); } @@ -406,6 +473,8 @@ public async Task CreateUpdatedEvent_ShouldReturnRollbackInvalidRouteNode_OnInte var afterNode = A.Fake(); var shadowTableRouteNode = A.Fake(); var appSettingsValue = new ApplicationSetting { Tolerance = 0.01 }; + var username = "myAwesomeUsername"; + afterNode.Username = username; A.CallTo(() => applicationSetting.Value).Returns(appSettingsValue); A.CallTo(() => afterNode.Mrid).Returns(Guid.NewGuid()); @@ -428,7 +497,12 @@ public async Task CreateUpdatedEvent_ShouldReturnRollbackInvalidRouteNode_OnInte var result = await factory.CreateUpdatedEvent(beforeNode, afterNode); var rollbackInvalidRouteNode = (RollbackInvalidRouteNode)result[0]; - var expected = new RollbackInvalidRouteNode(beforeNode, "Update to route node is invalid because it is insecting with route-segments."); + var expected = new RollbackInvalidRouteNode( + rollbackToNode: beforeNode, + message: "It is not allowed to change the geometry of a route node so it intersects with one or more route segments.", + errorCode: "ROUTE_NODE_GEOMETRY_UPDATE_NOT_ALLOWED_TO_INTERSECT_WITH_ROUTE_SEGMENT", + username: username + ); using (var scope = new AssertionScope()) { diff --git a/test/OpenFTTH.GDBIntegrator.Integrator.Tests/Factories/RouteSegmentCommandFactoryTest.cs b/test/OpenFTTH.GDBIntegrator.Integrator.Tests/Factories/RouteSegmentCommandFactoryTest.cs index bd7e349..07ecc83 100644 --- a/test/OpenFTTH.GDBIntegrator.Integrator.Tests/Factories/RouteSegmentCommandFactoryTest.cs +++ b/test/OpenFTTH.GDBIntegrator.Integrator.Tests/Factories/RouteSegmentCommandFactoryTest.cs @@ -69,7 +69,7 @@ public async Task CreateDigitizedEvent_ShouldReturnInvalidRouteSegmentOperation_ .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(false); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((false, "LINE_STRING_IS_INVALID")); var factory = new RouteSegmentCommandFactory(applicationSettings, routeSegmentValidator, geoDatabase, routeNodeFactory); @@ -102,7 +102,7 @@ public async Task CreateDigitizedEvent_ShouldReturnNewRouteSegmentDigitized_OnIn .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteNodes(routeSegment)) .Returns(intersectingStartNodes); @@ -143,7 +143,7 @@ public async Task CreateDigitizedEvent_ShouldReturnNewRouteSegmentDigitized_OnIn .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteNodes(routeSegment)) .Returns(intersectingStartNodes); @@ -184,7 +184,7 @@ public async Task CreateDigitizedEvent_ShouldReturnNewRouteSegmentDigitized_OnIn .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteNodes(routeSegment)) .Returns(intersectingStartNodes); @@ -225,7 +225,7 @@ public async Task CreateDigitizedEvent_ShouldReturnNewRouteSegmentDigitized_OnIn .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteNodes(routeSegment)) .Returns(intersectingStartNodes); @@ -262,7 +262,7 @@ public async Task CreateDigitizedEvent_ShouldCallInsertRouteSegmentIntegrator_On .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((true, null)); var factory = new RouteSegmentCommandFactory(applicationSettings, routeSegmentValidator, geoDatabase, routeNodeFactory); @@ -294,7 +294,7 @@ public async Task CreateDigitizedEvent_ShouldContainStartCreateExistingRouteSegm .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteNodes(routeSegment)) .Returns(intersectingStartNodes); @@ -345,7 +345,7 @@ public async Task CreateDigitizedEvent_ShouldContainEndCreateExistingRouteSegmen .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteNodes(routeSegment)) .Returns(intersectingStartNodes); @@ -398,7 +398,7 @@ public async Task CreateDigitizedEvent_ShouldContainEndCreateExistingRouteSegmen .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteNodes(routeSegment)) .Returns(intersectingStartNodes); @@ -456,7 +456,7 @@ public async Task CreateDigitizedEvent_ShouldReturnNewSegmentAndSplittedRouteSeg .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); A.CallTo(() => routeSegment.GetLineString()).Returns(lineString); - A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(lineString)).Returns((true, null)); A.CallTo(() => geoDatabase.GetAllIntersectingRouteNodesNotIncludingEdges(routeSegment)) .Returns(allIntersectingRouteNodes); @@ -493,7 +493,7 @@ public async Task CreateUpdatedEvent_ShouldReturnRouteSegmentMarkedForDeletion_O var routeSegmentAfter = A.Fake(); A.CallTo(() => routeSegmentAfter.GetLineString()).Returns(A.Fake()); - A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns((true, null)); A.CallTo(() => routeSegmentAfter.MarkAsDeleted).Returns(true); var factory = new RouteSegmentCommandFactory(applicationSettings, routeSegmentValidator, geoDatabase, routeNodeFactory); @@ -552,16 +552,21 @@ public async Task CreateUpdatedEvent_ShouldThrowException_OnGeometryBeingInvalid A.CallTo(() => routeSegmentAfter.GetGeoJsonCoordinate()).Returns("LINESTRING(578223.64355838 6179284.23759438, 578238.4182511 6179279.78494725)"); A.CallTo(() => routeSegmentShadowTable.GetGeoJsonCoordinate()).Returns("LINESTRING(578223.64355838 6179284.23759438, 378238.4182511 6179279.78494725)"); + //A.CallTo(() => routeSegmentShadowTable.Username).Returns("MyAwesomeUserName"); A.CallTo(() => routeSegmentAfter.MarkAsDeleted).Returns(false); A.CallTo(() => routeSegmentShadowTable.MarkAsDeleted).Returns(false); - A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns(false); + A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns((false, "LINE_STRING_IS_INVALID")); var factory = new RouteSegmentCommandFactory(applicationSettings, routeSegmentValidator, geoDatabase, routeNodeFactory); - Func act = async () => await factory.CreateUpdatedEvent(routeSegmentBefore, routeSegmentAfter); + var result = (await factory.CreateUpdatedEvent(routeSegmentBefore, routeSegmentAfter)).ToList(); - await act.Should().ThrowExactlyAsync("Linestring is not valid."); + using (var scope = new AssertionScope()) + { + result.Count().Should().Be(1); + result[0].Should().BeOfType(typeof(RollbackInvalidRouteSegment)); + } } [Fact] @@ -605,7 +610,7 @@ public async Task CreateUpdatedEvent_ShouldReturnTwoSplitEventsAndConnectivityCh A.CallTo(() => routeSegmentAfter.MarkAsDeleted).Returns(false); A.CallTo(() => routeSegmentShadowTable.MarkAsDeleted).Returns(false); - A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteSegments(routeSegmentAfter)).Returns(new List { A.Fake() }); A.CallTo(() => geoDatabase.GetIntersectingEndRouteSegments(routeSegmentAfter)).Returns(new List { A.Fake() }); @@ -650,7 +655,7 @@ public async Task CreateUpdatedEvent_ShouldReturnRouteSegmentLocationChanged_OnG A.CallTo(() => routeSegmentAfter.MarkAsDeleted).Returns(false); A.CallTo(() => routeSegmentShadowTable.MarkAsDeleted).Returns(false); - A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteSegments(routeSegmentAfter)).Returns(new List { A.Fake() }); A.CallTo(() => geoDatabase.GetIntersectingEndRouteSegments(routeSegmentAfter)).Returns(new List { A.Fake() }); @@ -692,7 +697,7 @@ public async Task CreateDigitizedEvent_ShouldReturnInvalidRouteSegmentOperation_ A.CallTo(() => applicationSettings.Value) .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); - A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegment.GetLineString())).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegment.GetLineString())).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteNodes(routeSegment)).Returns(new List { A.Fake(), A.Fake() }); var factory = new RouteSegmentCommandFactory(applicationSettings, routeSegmentValidator, geoDatabase, routeNodeFactory); @@ -718,7 +723,7 @@ public async Task CreateDigitizedEvent_ShouldReturnInvalidRouteSegmentOperation_ A.CallTo(() => applicationSettings.Value) .Returns(new ApplicationSetting { ApplicationName = "GDB_INTEGRATOR" }); - A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegment.GetLineString())).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegment.GetLineString())).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingEndRouteNodes(routeSegment)).Returns(new List { A.Fake(), A.Fake() }); var factory = new RouteSegmentCommandFactory(applicationSettings, routeSegmentValidator, geoDatabase, routeNodeFactory); @@ -752,7 +757,7 @@ public async Task CreateUpdatedEvent_ShouldThrowException_OnStartRouteNodeCountB A.CallTo(() => routeSegmentAfter.MarkAsDeleted).Returns(false); A.CallTo(() => routeSegmentShadowTable.MarkAsDeleted).Returns(false); - A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingStartRouteNodes(routeSegmentAfter)).Returns(new List { A.Fake(), A.Fake() }); var factory = new RouteSegmentCommandFactory(applicationSettings, routeSegmentValidator, geoDatabase, routeNodeFactory); @@ -782,7 +787,7 @@ public async Task CreateUpdatedEvent_ShouldThrowException_OnEndRouteNodeCountBei A.CallTo(() => routeSegmentAfter.MarkAsDeleted).Returns(false); A.CallTo(() => routeSegmentShadowTable.MarkAsDeleted).Returns(false); - A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns(true); + A.CallTo(() => routeSegmentValidator.LineIsValid(routeSegmentAfter.GetLineString())).Returns((true, null)); A.CallTo(() => geoDatabase.GetIntersectingEndRouteNodes(routeSegmentAfter)).Returns(new List { A.Fake(), A.Fake() }); var factory = new RouteSegmentCommandFactory(applicationSettings, routeSegmentValidator, geoDatabase, routeNodeFactory); diff --git a/test/OpenFTTH.GDBIntegrator.RouteNetwork.Tests/Validators/RouteSegmentValidatorTest.cs b/test/OpenFTTH.GDBIntegrator.RouteNetwork.Tests/Validators/RouteSegmentValidatorTest.cs index c90f17d..b4c8717 100644 --- a/test/OpenFTTH.GDBIntegrator.RouteNetwork.Tests/Validators/RouteSegmentValidatorTest.cs +++ b/test/OpenFTTH.GDBIntegrator.RouteNetwork.Tests/Validators/RouteSegmentValidatorTest.cs @@ -27,7 +27,7 @@ public void LineIsValid_ShouldReturnTrue_OnSimpleLine() var routeSegmentValidator = new RouteSegmentValidator(logger, applicationSettings); var result = routeSegmentValidator.LineIsValid(line); - result.Should().BeTrue(); + result.Should().BeEquivalentTo((true, (string)null)); } [Fact] @@ -49,7 +49,7 @@ public void LineIsValid_ShouldReturnFalse_OnEndsSnappedLine() var routeSegmentValidator = new RouteSegmentValidator(logger, applicationSettings); var result = routeSegmentValidator.LineIsValid(line); - result.Should().BeFalse(); + result.Should().BeEquivalentTo((false, "LINE_STRING_IS_CLOSED")); } [Fact] @@ -73,7 +73,7 @@ public void LineIsValid_ShouldReturnFalse_OnSelfIntersectingLine() var routeSegmentValidator = new RouteSegmentValidator(logger, applicationSettings); var result = routeSegmentValidator.LineIsValid(line); - result.Should().BeFalse(); + result.Should().BeEquivalentTo((false, "LINE_STRING_IS_NOT_SIMPLE")); } [Fact] @@ -97,7 +97,7 @@ public void LineIsValid_ShouldReturnFalse_OnEndSnappedToEdge() var routeSegmentValidator = new RouteSegmentValidator(logger, applicationSettings); var result = routeSegmentValidator.LineIsValid(line); - result.Should().BeFalse(); + result.Should().BeEquivalentTo((false, "LINE_STRING_IS_NOT_SIMPLE")); } [Fact] @@ -119,7 +119,7 @@ public void LineIsValid_ShouldReturnTrue_OnEndsDistanceAtTolerance() var routeSegmentValidator = new RouteSegmentValidator(logger, applicationSettings); var result = routeSegmentValidator.LineIsValid(line); - result.Should().BeTrue(); + result.Should().BeEquivalentTo((true, (string)null)); } [Fact] @@ -141,7 +141,7 @@ public void LineIsValid_ShouldReturnFalse_OnEndsDistanceLessThanTolerance() var routeSegmentValidator = new RouteSegmentValidator(logger, applicationSettings); var result = routeSegmentValidator.LineIsValid(line); - result.Should().BeFalse(); + result.Should().BeEquivalentTo((false, "LINE_STRING_ENDS_CLOSER_TO_EACH_OTHER_THAN_TOLERANCE")); } [Fact] @@ -177,7 +177,7 @@ public void LineIsValid_ShouldReturnTrue_OnEdgeDistanceMoreThanTolerance() // Assert that distance from end point to edge is 0.023 endPointToEdgeDistance.Should().Be(0.023); - result.Should().BeTrue(); + result.Should().BeEquivalentTo((true, (string)null)); } [Fact] @@ -214,7 +214,7 @@ public void LineIsValid_ShouldReturnFalse_OnEdgeDistanceMoreLessTolerance() // Assert that distance from end point to edge is 0.007 endPointToEdgeDistance.Should().Be(0.007); - result.Should().BeFalse(); + result.Should().BeEquivalentTo((false, "LINE_STRING_ENDS_CLOSER_TO_THE_EDGE_THAN_TOLERANCE"));; } } }