diff --git a/VideoAPI/Testing.Common/Helper/ApiUriFactory.cs b/VideoAPI/Testing.Common/Helper/ApiUriFactory.cs index 94be7c1b7..79786f813 100644 --- a/VideoAPI/Testing.Common/Helper/ApiUriFactory.cs +++ b/VideoAPI/Testing.Common/Helper/ApiUriFactory.cs @@ -71,6 +71,7 @@ public class ConsultationEndpoints public string HandleConsultationRequest => $"{ApiRoot}"; public string LeaveConsultationRequest => $"{ApiRoot}/leave"; + public string RespondToAdminConsultationRequest => $"{ApiRoot}/vhofficer/respond"; } diff --git a/VideoAPI/Video.API/Controllers/ConsultationController.cs b/VideoAPI/Video.API/Controllers/ConsultationController.cs index 948e80c21..600c304ab 100644 --- a/VideoAPI/Video.API/Controllers/ConsultationController.cs +++ b/VideoAPI/Video.API/Controllers/ConsultationController.cs @@ -122,6 +122,7 @@ await NotifyConsultationResponse(conference, requestedBy, requestedFor, [HttpPost("leave")] [SwaggerOperation(OperationId = "LeavePrivateConsultation")] [ProducesResponseType((int) HttpStatusCode.NoContent)] + [ProducesResponseType((int) HttpStatusCode.NotFound)] [ProducesResponseType((int) HttpStatusCode.BadRequest)] public async Task LeavePrivateConsultation(LeaveConsultationRequest request) { @@ -159,6 +160,44 @@ public async Task LeavePrivateConsultation(LeaveConsultationReque return NoContent(); } + [HttpPost("vhofficer/respond")] + [SwaggerOperation(OperationId = "RespondToAdminConsultationRequest")] + [ProducesResponseType((int) HttpStatusCode.NoContent)] + [ProducesResponseType((int) HttpStatusCode.NotFound)] + [ProducesResponseType((int) HttpStatusCode.BadRequest)] + public async Task RespondToAdminConsultationRequest(AdminConsultationRequest request) + { + _logger.LogDebug($"RespondToAdminConsultationRequest"); + + var getConferenceByIdQuery = new GetConferenceByIdQuery(request.ConferenceId); + var conference = + await _queryHandler.Handle(getConferenceByIdQuery); + + if (conference == null) + { + _logger.LogError($"Unable to find conference {request.ConferenceId}"); + return NotFound(); + } + + var participant = conference.GetParticipants().SingleOrDefault(x => x.Id == request.ParticipantId); + if (participant == null) + { + _logger.LogError($"Unable to find participant request by with id {request.ParticipantId}"); + return NotFound(); + } + + if (request.Answer.Value == ConsultationAnswer.Accepted) + { + await _videoPlatformService.TransferParticipantAsync(conference.Id, participant.Id, + participant.CurrentRoom.Value, request.ConsultationRoom); + await _hubContext.Clients.Group(participant.Username.ToLowerInvariant()).AdminConsultationMessage + (conference.Id, request.ConsultationRoom, participant.Username.ToLowerInvariant(), + ConsultationAnswer.Accepted); + } + + return NoContent(); + } + /// /// This method raises a notification to the requestee informing them of an incoming consultation request /// diff --git a/VideoAPI/Video.API/Validations/AdminConsultationRequestValidation.cs b/VideoAPI/Video.API/Validations/AdminConsultationRequestValidation.cs new file mode 100644 index 000000000..b4f79a2b9 --- /dev/null +++ b/VideoAPI/Video.API/Validations/AdminConsultationRequestValidation.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using FluentValidation; +using VideoApi.Contract.Requests; +using VideoApi.Domain.Enums; + +namespace Video.API.Validations +{ + public class AdminConsultationRequestValidation : AbstractValidator + { + public static readonly string NoConferenceIdErrorMessage = "ConferenceId is required"; + public static readonly string NoParticipantIdErrorMessage = "ParticipantId is required"; + public static readonly string NoAnswerErrorMessage = "Answer to request is required"; + public static readonly string NotValidConsultationRoomMessage = "Room must be a consultation room"; + + public AdminConsultationRequestValidation() + { + RuleFor(x => x.ConferenceId).NotEmpty().WithMessage(NoConferenceIdErrorMessage); + RuleFor(x => x.ParticipantId).NotEmpty().WithMessage(NoParticipantIdErrorMessage); + RuleFor(x => x.Answer).NotEqual(ConsultationAnswer.None).WithMessage(NoAnswerErrorMessage); + RuleFor(x => x.ConsultationRoom).Custom((type, context) => + { + var validRooms = new List {RoomType.ConsultationRoom1, RoomType.ConsultationRoom2}; + if (!validRooms.Contains(type)) + { + context.AddFailure("ConsultationRoom", NotValidConsultationRoomMessage); + } + }); + } + } +} \ No newline at end of file diff --git a/VideoAPI/VideoApi.Contract/Requests/AdminConsultationRequest.cs b/VideoAPI/VideoApi.Contract/Requests/AdminConsultationRequest.cs new file mode 100644 index 000000000..462498800 --- /dev/null +++ b/VideoAPI/VideoApi.Contract/Requests/AdminConsultationRequest.cs @@ -0,0 +1,33 @@ +using System; +using System.ComponentModel.DataAnnotations; +using VideoApi.Domain.Enums; + +namespace VideoApi.Contract.Requests +{ + /// + /// Request a private consultation with another participant + /// + public class AdminConsultationRequest + { + /// + /// The conference UUID + /// + public Guid ConferenceId { get; set; } + + /// + /// UUID of participant VH Officer attempted to call + /// + public Guid ParticipantId { get; set; } + + /// + /// Consultation Room to use + /// + public RoomType ConsultationRoom { get; set; } + + /// + /// Response to a consultation request (i.e. 'Accepted or Rejected') + /// + [EnumDataType(typeof(ConsultationAnswer))] + public ConsultationAnswer? Answer { get; set; } + } +} \ No newline at end of file diff --git a/VideoAPI/VideoApi.Contract/Requests/ConsultationRequest.cs b/VideoAPI/VideoApi.Contract/Requests/ConsultationRequest.cs index 115cb958b..503b7c642 100644 --- a/VideoAPI/VideoApi.Contract/Requests/ConsultationRequest.cs +++ b/VideoAPI/VideoApi.Contract/Requests/ConsultationRequest.cs @@ -29,7 +29,7 @@ public class ConsultationRequest [EnumDataType(typeof(ConsultationAnswer))] public ConsultationAnswer? Answer { get; set; } } - + public enum ConsultationAnswer { /// diff --git a/VideoAPI/VideoApi.Domain/Enums/EventType.cs b/VideoAPI/VideoApi.Domain/Enums/EventType.cs index e00a35f47..626292161 100644 --- a/VideoAPI/VideoApi.Domain/Enums/EventType.cs +++ b/VideoAPI/VideoApi.Domain/Enums/EventType.cs @@ -15,6 +15,7 @@ public enum EventType MediaPermissionDenied, ParticipantJoining, SelfTestFailed, - Suspend + Suspend, + VhoCall } } \ No newline at end of file diff --git a/VideoAPI/VideoApi.Events/Handlers/Core/EventHandlerBase.cs b/VideoAPI/VideoApi.Events/Handlers/Core/EventHandlerBase.cs index b6badd466..409d235d2 100644 --- a/VideoAPI/VideoApi.Events/Handlers/Core/EventHandlerBase.cs +++ b/VideoAPI/VideoApi.Events/Handlers/Core/EventHandlerBase.cs @@ -1,5 +1,4 @@ using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; using VideoApi.DAL.Commands.Core; using VideoApi.DAL.Exceptions; diff --git a/VideoAPI/VideoApi.Events/Handlers/VhOfficerCallEventHandler.cs b/VideoAPI/VideoApi.Events/Handlers/VhOfficerCallEventHandler.cs new file mode 100644 index 000000000..7ad9aa090 --- /dev/null +++ b/VideoAPI/VideoApi.Events/Handlers/VhOfficerCallEventHandler.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.SignalR; +using VideoApi.DAL.Commands.Core; +using VideoApi.DAL.Queries.Core; +using VideoApi.Domain.Enums; +using VideoApi.Events.Handlers.Core; +using VideoApi.Events.Hub; +using VideoApi.Events.Models; +using VideoApi.Events.ServiceBus; + +namespace VideoApi.Events.Handlers +{ + public class VhOfficerCallEventHandler : EventHandlerBase + { + public VhOfficerCallEventHandler(IQueryHandler queryHandler, ICommandHandler commandHandler, + IServiceBusQueueClient serviceBusQueueClient, IHubContext hubContext) : base( + queryHandler, commandHandler, serviceBusQueueClient, hubContext) + { + } + + public override EventType EventType => EventType.VhoCall; + + protected override async Task PublishStatusAsync(CallbackEvent callbackEvent) + { + var targetRoom = ValidationConsultationRoom(callbackEvent); + await HubContext.Clients.Group(SourceParticipant.Username.ToLowerInvariant()) + .AdminConsultationMessage(SourceConference.Id, targetRoom, + SourceParticipant.Username.ToLowerInvariant()); + } + + private RoomType ValidationConsultationRoom(CallbackEvent callbackEvent) + { + if (!callbackEvent.TransferTo.HasValue || callbackEvent.TransferTo.Value != RoomType.ConsultationRoom1 + && callbackEvent.TransferTo.Value != RoomType.ConsultationRoom2) + { + throw new ArgumentException("No consultation room provided"); + } + + return callbackEvent.TransferTo.Value; + } + } +} \ No newline at end of file diff --git a/VideoAPI/VideoApi.Events/Hub/EventHubClient.cs b/VideoAPI/VideoApi.Events/Hub/EventHubClient.cs index d82299a44..b0113ca9a 100644 --- a/VideoAPI/VideoApi.Events/Hub/EventHubClient.cs +++ b/VideoAPI/VideoApi.Events/Hub/EventHubClient.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.SignalR; +using VideoApi.Contract.Requests; using VideoApi.Domain.Enums; using VideoApi.Services; @@ -12,6 +13,9 @@ public interface IEventHubClient Task ParticipantStatusMessage(string email, ParticipantState participantState); Task ConferenceStatusMessage(Guid conferenceId, ConferenceState conferenceState); Task ConsultationMessage(Guid conferenceId, string requestedBy, string requestedFor, string result); + + Task AdminConsultationMessage(Guid conferenceId, RoomType room, string requestedFor, + ConsultationAnswer? answer = null); Task HelpMessage(Guid conferenceId, string participantName); } diff --git a/VideoAPI/VideoApi.Events/VideoApi.Events.csproj b/VideoAPI/VideoApi.Events/VideoApi.Events.csproj index efeb6e3d8..024e4246b 100644 --- a/VideoAPI/VideoApi.Events/VideoApi.Events.csproj +++ b/VideoAPI/VideoApi.Events/VideoApi.Events.csproj @@ -12,6 +12,7 @@ + diff --git a/VideoAPI/VideoApi.IntegrationTests/Api/Consultations.feature b/VideoAPI/VideoApi.IntegrationTests/Api/Consultations.feature deleted file mode 100644 index 700ba030a..000000000 --- a/VideoAPI/VideoApi.IntegrationTests/Api/Consultations.feature +++ /dev/null @@ -1,105 +0,0 @@ -Feature: Consultations - In order to manage private consultations - As an API service - I want to raise and respond to private consultations - - Scenario: Successfully raise a private consultation request - Given I have a conference - And I have a valid raise consultation request - When I send the request to the endpoint - Then the response should have the status NoContent and success status True - - Scenario: Raise private consultation request against non-existent conference - Given I have a conference - And I have a nonexistent raise consultation request - When I send the request to the endpoint - Then the response should have the status NotFound and success status False - - Scenario: Raise a private consultation request that fails validation - Given I have an invalid raise consultation request - When I send the request to the endpoint - Then the response should have the status BadRequest and success status False - And the error response message should also contain 'RequestedBy is required' - And the error response message should also contain 'RequestedFor is required' - And the error response message should also contain 'ConferenceId is required' - - Scenario: Raise private consultation request when user requested by does not exist - Given I have a conference - And I have a raise consultation request with an invalid requestedBy - When I send the request to the endpoint - Then the response should have the status NotFound and success status False - - Scenario: Raise private consultation request when user requested for does not exist - Given I have a conference - And I have a raise consultation request with an invalid requestedFor - When I send the request to the endpoint - Then the response should have the status NotFound and success status False - - - ### Respond to consultation - - Scenario: Successfully respond to a private consultation request - Given I have a conference - And I have a valid respond consultation request - When I send the request to the endpoint - Then the response should have the status NoContent and success status True - - Scenario: Respond to private consultation request against non-existent conference - Given I have a conference - And I have a nonexistent respond consultation request - When I send the request to the endpoint - Then the response should have the status NotFound and success status False - - Scenario: Respond to a private consultation request that fails validation - Given I have an invalid respond consultation request - When I send the request to the endpoint - Then the response should have the status BadRequest and success status False - And the error response message should also contain 'RequestedBy is required' - And the error response message should also contain 'RequestedFor is required' - And the error response message should also contain 'ConferenceId is required' - And the error response message should also contain 'Answer to request is required' - - Scenario: Respond to private consultation request when user requested by does not exist - Given I have a conference - And I have a respond consultation request with an invalid requestedBy - When I send the request to the endpoint - Then the response should have the status NotFound and success status False - - Scenario: Respond to private consultation request when user requested for does not exist - Given I have a conference - And I have a respond consultation request with an invalid requestedFor - When I send the request to the endpoint - Then the response should have the status NotFound and success status False - - ### Leave a consultation - Scenario: Leave a private consultation with an invalid request - Given I have an invalid leave consultation request - When I send the request to the endpoint - Then the response should have the status BadRequest and success status False - And the error response message should also contain 'ConferenceId is required' - And the error response message should also contain 'ParticipantId is required' - - Scenario: Successfully leave a private consultation - Given I have a conference - And I have an valid leave consultation request - When I send the request to the endpoint - Then the response should have the status NoContent and success status True - - Scenario: Leaving a consultation for a non-existent conference returns not found - Given I have a conference - And I have an nonexistent leave consultation request - When I send the request to the endpoint - Then the response should have the status NotFound and success status False - - Scenario: Leaving a consultation for a non-existent participant returns not found - Given I have a conference - And I have a leave consultation request for a nonexistent participant - When I send the request to the endpoint - Then the response should have the status NotFound and success status False - - Scenario: Leaving a consultation for a participant not in a consultation returns a bad request - Given I have a conference - And I have a leave consultation request for a participant not in a consultation - When I send the request to the endpoint - Then the response should have the status BadRequest and success status False - And the error response message should also contain 'is not in a consultation room' \ No newline at end of file diff --git a/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/AdminConsultations.feature b/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/AdminConsultations.feature new file mode 100644 index 000000000..977fb4954 --- /dev/null +++ b/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/AdminConsultations.feature @@ -0,0 +1,31 @@ +Feature: Respond to Admin Consultations + In order to manage private consultations with the VH Admin team + As an API service + I want to respond to private consultations with the VH Admin team + + Scenario: Respond to a VH Officer Consultation with an invalid request + Given I have an invalid respond to admin consultation request + When I send the request to the endpoint + Then the response should have the status BadRequest and success status False + And the error response message should also contain 'ConferenceId is required' + And the error response message should also contain 'ParticipantId is required' + And the error response message should also contain 'Room must be a consultation room' + And the error response message should also contain 'Answer to request is required' + + Scenario: Respond to a VH Officer Consultation for a non-existent conference + Given I have a conference + Given I have a nonexistent respond to admin consultation request + When I send the request to the endpoint + Then the response should have the status NotFound and success status False + + Scenario: Respond to a VH Officer Consultation for a non-existent participant + Given I have a conference + Given I have a respond to admin consultation request with a non-existent participant + When I send the request to the endpoint + Then the response should have the status NotFound and success status False + + Scenario: Respond to a VH Officer Consultation successfully + Given I have a conference + And I have a valid respond to admin consultation request + When I send the request to the endpoint + Then the response should have the status NoContent and success status True \ No newline at end of file diff --git a/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/LeavePrivateConsultation.feature b/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/LeavePrivateConsultation.feature new file mode 100644 index 000000000..50dbac93e --- /dev/null +++ b/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/LeavePrivateConsultation.feature @@ -0,0 +1,36 @@ +Feature: Leave Private Consultations + In order to manage private consultations + As an API service + I want to leave private consultations + + Scenario: Leave a private consultation with an invalid request + Given I have an invalid leave consultation request + When I send the request to the endpoint + Then the response should have the status BadRequest and success status False + And the error response message should also contain 'ConferenceId is required' + And the error response message should also contain 'ParticipantId is required' + + Scenario: Successfully leave a private consultation + Given I have a conference + And I have an valid leave consultation request + When I send the request to the endpoint + Then the response should have the status NoContent and success status True + + Scenario: Leaving a consultation for a non-existent conference returns not found + Given I have a conference + And I have an nonexistent leave consultation request + When I send the request to the endpoint + Then the response should have the status NotFound and success status False + + Scenario: Leaving a consultation for a non-existent participant returns not found + Given I have a conference + And I have a leave consultation request for a nonexistent participant + When I send the request to the endpoint + Then the response should have the status NotFound and success status False + + Scenario: Leaving a consultation for a participant not in a consultation returns a bad request + Given I have a conference + And I have a leave consultation request for a participant not in a consultation + When I send the request to the endpoint + Then the response should have the status BadRequest and success status False + And the error response message should also contain 'is not in a consultation room' \ No newline at end of file diff --git a/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/RaisePrivateConsultation.feature b/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/RaisePrivateConsultation.feature new file mode 100644 index 000000000..c8134eb68 --- /dev/null +++ b/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/RaisePrivateConsultation.feature @@ -0,0 +1,36 @@ +Feature: Raise Private Consultations + In order to manage private consultations + As an API service + I want to raise private consultations + + Scenario: Successfully raise a private consultation request + Given I have a conference + And I have a valid raise consultation request + When I send the request to the endpoint + Then the response should have the status NoContent and success status True + + Scenario: Raise private consultation request against non-existent conference + Given I have a conference + And I have a nonexistent raise consultation request + When I send the request to the endpoint + Then the response should have the status NotFound and success status False + + Scenario: Raise a private consultation request that fails validation + Given I have an invalid raise consultation request + When I send the request to the endpoint + Then the response should have the status BadRequest and success status False + And the error response message should also contain 'RequestedBy is required' + And the error response message should also contain 'RequestedFor is required' + And the error response message should also contain 'ConferenceId is required' + + Scenario: Raise private consultation request when user requested by does not exist + Given I have a conference + And I have a raise consultation request with an invalid requestedBy + When I send the request to the endpoint + Then the response should have the status NotFound and success status False + + Scenario: Raise private consultation request when user requested for does not exist + Given I have a conference + And I have a raise consultation request with an invalid requestedFor + When I send the request to the endpoint + Then the response should have the status NotFound and success status False \ No newline at end of file diff --git a/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/RespondToPrivateConsultation.feature b/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/RespondToPrivateConsultation.feature new file mode 100644 index 000000000..6ad5ada4e --- /dev/null +++ b/VideoAPI/VideoApi.IntegrationTests/Api/Consultations/RespondToPrivateConsultation.feature @@ -0,0 +1,37 @@ +Feature: Respond To Consultations + In order to manage private consultations + As an API service + I want to respond to private consultations + + Scenario: Successfully respond to a private consultation request + Given I have a conference + And I have a valid respond consultation request + When I send the request to the endpoint + Then the response should have the status NoContent and success status True + + Scenario: Respond to private consultation request against non-existent conference + Given I have a conference + And I have a nonexistent respond consultation request + When I send the request to the endpoint + Then the response should have the status NotFound and success status False + + Scenario: Respond to a private consultation request that fails validation + Given I have an invalid respond consultation request + When I send the request to the endpoint + Then the response should have the status BadRequest and success status False + And the error response message should also contain 'RequestedBy is required' + And the error response message should also contain 'RequestedFor is required' + And the error response message should also contain 'ConferenceId is required' + And the error response message should also contain 'Answer to request is required' + + Scenario: Respond to private consultation request when user requested by does not exist + Given I have a conference + And I have a respond consultation request with an invalid requestedBy + When I send the request to the endpoint + Then the response should have the status NotFound and success status False + + Scenario: Respond to private consultation request when user requested for does not exist + Given I have a conference + And I have a respond consultation request with an invalid requestedFor + When I send the request to the endpoint + Then the response should have the status NotFound and success status False \ No newline at end of file diff --git a/VideoAPI/VideoApi.IntegrationTests/Steps/ConsultationSteps.cs b/VideoAPI/VideoApi.IntegrationTests/Steps/ConsultationSteps.cs index b9dbc7269..1b083bcb4 100644 --- a/VideoAPI/VideoApi.IntegrationTests/Steps/ConsultationSteps.cs +++ b/VideoAPI/VideoApi.IntegrationTests/Steps/ConsultationSteps.cs @@ -174,6 +174,43 @@ public async Task GivenNoConsultationRoomsAreAvailable() await db.SaveChangesAsync(); } } + + [Given(@"I have a (.*) respond to admin consultation request")] + [Given(@"I have an (.*) respond to admin consultation request")] + public void GivenIHaveARespondToAdminConsultationRequest(Scenario scenario) + { + var request = SetupRespondToAdminConsultationRequest(); + switch (scenario) + { + case Scenario.Valid: break; + case Scenario.Invalid: + { + request.ConferenceId = Guid.Empty; + request.Answer = ConsultationAnswer.None; + request.ParticipantId = Guid.Empty; + request.ConsultationRoom = RoomType.HearingRoom; + break; + } + + case Scenario.Nonexistent: + { + request.ConferenceId = Guid.NewGuid(); + } + break; + default: throw new ArgumentOutOfRangeException(nameof(scenario), scenario, null); + } + + SerialiseRespondToAdminConsultationRequest(request); + } + + [Given(@"I have a respond to admin consultation request with a non-existent participant")] + public void GivenIHaveARespondToAdminConsultationRequestWithNonExistentParticipant() + { + var request = SetupRespondToAdminConsultationRequest(); + request.ParticipantId = Guid.NewGuid(); + + SerialiseRespondToAdminConsultationRequest(request); + } private void SerialiseConsultationRequest(ConsultationRequest request) { @@ -183,6 +220,14 @@ private void SerialiseConsultationRequest(ConsultationRequest request) ApiTestContext.HttpContent = new StringContent(jsonBody, Encoding.UTF8, "application/json"); } + private void SerialiseRespondToAdminConsultationRequest(AdminConsultationRequest request) + { + ApiTestContext.Uri = _endpoints.RespondToAdminConsultationRequest; + ApiTestContext.HttpMethod = HttpMethod.Post; + var jsonBody = ApiRequestHelper.SerialiseRequestToSnakeCaseJson(request); + ApiTestContext.HttpContent = new StringContent(jsonBody, Encoding.UTF8, "application/json"); + } + private void SerialiseLeaveConsultationRequest(LeaveConsultationRequest request) { ApiTestContext.Uri = _endpoints.LeaveConsultationRequest; @@ -250,5 +295,27 @@ private async Task SetupLeaveConsultationRequest(bool return request; } + private AdminConsultationRequest SetupRespondToAdminConsultationRequest() + { + var request = new AdminConsultationRequest(); + + var seededConference = _conferenceTestContext.SeededConference; + + if (seededConference == null) + { + return request; + } + + var participants = seededConference.GetParticipants().Where(x => + x.UserRole == UserRole.Individual || x.UserRole == UserRole.Representative).ToList(); + + request.ConferenceId = seededConference.Id; + request.ParticipantId = participants[0].Id; + request.Answer = ConsultationAnswer.Accepted; + request.ConsultationRoom = RoomType.ConsultationRoom1; + + return request; + } + } } \ No newline at end of file diff --git a/VideoAPI/VideoApi.IntegrationTests/VideoApi.IntegrationTests.csproj b/VideoAPI/VideoApi.IntegrationTests/VideoApi.IntegrationTests.csproj index e6d9f19e7..f2c70d618 100644 --- a/VideoAPI/VideoApi.IntegrationTests/VideoApi.IntegrationTests.csproj +++ b/VideoAPI/VideoApi.IntegrationTests/VideoApi.IntegrationTests.csproj @@ -29,5 +29,9 @@ + + + + \ No newline at end of file diff --git a/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/ConsultationControllerTestBase.cs b/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/ConsultationControllerTestBase.cs new file mode 100644 index 000000000..c1f52c563 --- /dev/null +++ b/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/ConsultationControllerTestBase.cs @@ -0,0 +1,70 @@ +using Microsoft.AspNetCore.SignalR; +using Microsoft.Extensions.Logging; +using Moq; +using NUnit.Framework; +using Testing.Common.Helper.Builders.Domain; +using Video.API.Controllers; +using VideoApi.DAL.Commands; +using VideoApi.DAL.Commands.Core; +using VideoApi.DAL.Queries; +using VideoApi.DAL.Queries.Core; +using VideoApi.Domain; +using VideoApi.Domain.Enums; +using VideoApi.Events.Hub; +using VideoApi.Services; +using Task = System.Threading.Tasks.Task; + +namespace VideoApi.UnitTests.Controllers.Consultation +{ + public abstract class ConsultationControllerTestBase + { + protected Mock CommandHandlerMock; + protected ConsultationController Controller; + protected Mock EventHubClientMock; + protected Mock> HubContextMock; + protected Mock QueryHandlerMock; + protected Mock> MockLogger; + protected Mock VideoPlatformServiceMock; + + protected Conference TestConference; + + [SetUp] + public void Setup() + { + QueryHandlerMock = new Mock(); + CommandHandlerMock = new Mock(); + HubContextMock = new Mock>(); + EventHubClientMock = new Mock(); + MockLogger = new Mock>(); + VideoPlatformServiceMock = new Mock(); + + TestConference = new ConferenceBuilder() + .WithParticipant(UserRole.Judge, null) + .WithParticipant(UserRole.Individual, "Claimant") + .WithParticipant(UserRole.Representative, "Claimant") + .WithParticipant(UserRole.Individual, "Defendant") + .WithParticipant(UserRole.Representative, "Defendant") + .Build(); + + QueryHandlerMock + .Setup(x => x.Handle(It.IsAny())) + .ReturnsAsync(TestConference); + + CommandHandlerMock + .Setup(x => x.Handle(It.IsAny())) + .Returns(Task.FromResult(default(object))); + + Controller = new ConsultationController(QueryHandlerMock.Object, CommandHandlerMock.Object, + HubContextMock.Object, MockLogger.Object, VideoPlatformServiceMock.Object); + + foreach (var participant in TestConference.GetParticipants()) + { + HubContextMock.Setup(x => x.Clients.Group(participant.Username.ToString())) + .Returns(EventHubClientMock.Object); + } + + HubContextMock.Setup(x => x.Clients.Group(EventHub.VhOfficersGroupName)) + .Returns(EventHubClientMock.Object); + } + } +} \ No newline at end of file diff --git a/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/HandleConsultationRequestTests.cs b/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/HandleConsultationRequestTests.cs index 8f64035f0..9667d3871 100644 --- a/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/HandleConsultationRequestTests.cs +++ b/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/HandleConsultationRequestTests.cs @@ -1,82 +1,24 @@ using System.Net; using FluentAssertions; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.SignalR; -using Microsoft.Extensions.Logging; using Moq; using NUnit.Framework; -using Testing.Common.Helper.Builders.Domain; -using Video.API.Controllers; using VideoApi.Contract.Requests; -using VideoApi.DAL.Commands; -using VideoApi.DAL.Commands.Core; -using VideoApi.DAL.Queries; -using VideoApi.DAL.Queries.Core; -using VideoApi.Domain; using VideoApi.Domain.Enums; using VideoApi.Domain.Validations; using VideoApi.Events.Hub; -using VideoApi.Services; using Task = System.Threading.Tasks.Task; namespace VideoApi.UnitTests.Controllers.Consultation { - public class HandleConsultationRequestTests + public class HandleConsultationRequestTests : ConsultationControllerTestBase { - private Mock _commandHandlerMock; - private ConsultationController _controller; - private Mock _eventHubClientMock; - private Mock> _hubContextMock; - private Mock _queryHandlerMock; - private Mock> _mockLogger; - private Mock _videoPlatformServiceMock; - - private Conference _testConference; - - [SetUp] - public void Setup() - { - _queryHandlerMock = new Mock(); - _commandHandlerMock = new Mock(); - _hubContextMock = new Mock>(); - _eventHubClientMock = new Mock(); - _mockLogger = new Mock>(); - _videoPlatformServiceMock = new Mock(); - - _testConference = new ConferenceBuilder() - .WithParticipant(UserRole.Judge, null) - .WithParticipant(UserRole.Individual, "Claimant") - .WithParticipant(UserRole.Representative, "Claimant") - .WithParticipant(UserRole.Individual, "Defendant") - .WithParticipant(UserRole.Representative, "Defendant") - .Build(); - - _queryHandlerMock - .Setup(x => x.Handle(It.IsAny())) - .ReturnsAsync(_testConference); - - _commandHandlerMock - .Setup(x => x.Handle(It.IsAny())) - .Returns(Task.FromResult(default(object))); - - _controller = new ConsultationController(_queryHandlerMock.Object, _commandHandlerMock.Object, - _hubContextMock.Object, _mockLogger.Object, _videoPlatformServiceMock.Object); - - foreach (var participant in _testConference.GetParticipants()) - { - _hubContextMock.Setup(x => x.Clients.Group(participant.Username.ToString())) - .Returns(_eventHubClientMock.Object); - } - _hubContextMock.Setup(x => x.Clients.Group(EventHub.VhOfficersGroupName)) - .Returns(_eventHubClientMock.Object); - } - [Test] public async Task should_raise_notification_to_requestee_when_consultation_is_requested() { - var conferenceId = _testConference.Id; - var requestedBy = _testConference.GetParticipants()[2]; - var requestedFor = _testConference.GetParticipants()[3]; + var conferenceId = TestConference.Id; + var requestedBy = TestConference.GetParticipants()[2]; + var requestedFor = TestConference.GetParticipants()[3]; var request = new ConsultationRequest { @@ -84,13 +26,13 @@ public async Task should_raise_notification_to_requestee_when_consultation_is_re RequestedBy = requestedBy.Id, RequestedFor = requestedFor.Id }; - await _controller.HandleConsultationRequest(request); + await Controller.HandleConsultationRequest(request); - _hubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Never); - _hubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Once); - _hubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Never); + HubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Never); + HubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Once); + HubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Never); - _eventHubClientMock.Verify( + EventHubClientMock.Verify( x => x.ConsultationMessage(conferenceId, requestedBy.Username, requestedFor.Username, string.Empty), Times.Once); } @@ -98,9 +40,9 @@ public async Task should_raise_notification_to_requestee_when_consultation_is_re [Test] public async Task should_raise_notification_to_requester_consultation_is_rejected() { - var conferenceId = _testConference.Id; - var requestedBy = _testConference.GetParticipants()[2]; - var requestedFor = _testConference.GetParticipants()[3]; + var conferenceId = TestConference.Id; + var requestedBy = TestConference.GetParticipants()[2]; + var requestedFor = TestConference.GetParticipants()[3]; var answer = ConsultationAnswer.Rejected; var request = new ConsultationRequest @@ -111,13 +53,13 @@ public async Task should_raise_notification_to_requester_consultation_is_rejecte Answer = answer }; - await _controller.HandleConsultationRequest(request); + await Controller.HandleConsultationRequest(request); - _hubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Once); - _hubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Never); - _hubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Never); + HubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Once); + HubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Never); + HubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Never); - _eventHubClientMock.Verify( + EventHubClientMock.Verify( x => x.ConsultationMessage(conferenceId, requestedBy.Username, requestedFor.Username, answer.ToString()), Times.Once); } @@ -125,9 +67,9 @@ public async Task should_raise_notification_to_requester_consultation_is_rejecte [Test] public async Task should_raise_notification_to_requester_and_admin_when_consultation_is_accepted() { - var conferenceId = _testConference.Id; - var requestedBy = _testConference.GetParticipants()[2]; - var requestedFor = _testConference.GetParticipants()[3]; + var conferenceId = TestConference.Id; + var requestedBy = TestConference.GetParticipants()[2]; + var requestedFor = TestConference.GetParticipants()[3]; var answer = ConsultationAnswer.Accepted; @@ -139,26 +81,26 @@ public async Task should_raise_notification_to_requester_and_admin_when_consulta Answer = answer }; - await _controller.HandleConsultationRequest(request); + await Controller.HandleConsultationRequest(request); - _hubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Once); - _hubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Never); - _hubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Once); - _eventHubClientMock.Verify( + HubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Once); + HubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Never); + HubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Once); + EventHubClientMock.Verify( x => x.ConsultationMessage(conferenceId, requestedBy.Username, requestedFor.Username, answer.ToString()), Times.Exactly(2)); - _videoPlatformServiceMock.Verify(x => - x.StartPrivateConsultationAsync(_testConference, requestedBy, requestedFor), Times.Once); - _videoPlatformServiceMock.VerifyNoOtherCalls(); + VideoPlatformServiceMock.Verify(x => + x.StartPrivateConsultationAsync(TestConference, requestedBy, requestedFor), Times.Once); + VideoPlatformServiceMock.VerifyNoOtherCalls(); } [Test] public async Task should_raise_notification_to_requestee_when_consultation_is_cancelled() { - var conferenceId = _testConference.Id; - var requestedBy = _testConference.GetParticipants()[2]; - var requestedFor = _testConference.GetParticipants()[3]; + var conferenceId = TestConference.Id; + var requestedBy = TestConference.GetParticipants()[2]; + var requestedFor = TestConference.GetParticipants()[3]; var request = new ConsultationRequest { @@ -167,13 +109,13 @@ public async Task should_raise_notification_to_requestee_when_consultation_is_ca RequestedFor = requestedFor.Id, Answer = ConsultationAnswer.Cancelled }; - await _controller.HandleConsultationRequest(request); + await Controller.HandleConsultationRequest(request); - _hubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Never); - _hubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Once); - _hubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Never); + HubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Never); + HubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Once); + HubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Never); - _eventHubClientMock.Verify( + EventHubClientMock.Verify( x => x.ConsultationMessage(conferenceId, requestedBy.Username, requestedFor.Username, ConsultationAnswer.Cancelled.ToString()), Times.Once); @@ -182,17 +124,17 @@ public async Task should_raise_notification_to_requestee_when_consultation_is_ca [Test] public async Task should_return_error_when_consultation_accepted_but_no_room_is_available() { - var conferenceId = _testConference.Id; - var requestedBy = _testConference.GetParticipants()[2]; - var requestedFor = _testConference.GetParticipants()[3]; + var conferenceId = TestConference.Id; + var requestedBy = TestConference.GetParticipants()[2]; + var requestedFor = TestConference.GetParticipants()[3]; - _videoPlatformServiceMock - .Setup(x => x.StartPrivateConsultationAsync(_testConference, requestedBy, requestedFor)) + VideoPlatformServiceMock + .Setup(x => x.StartPrivateConsultationAsync(TestConference, requestedBy, requestedFor)) .ThrowsAsync(new DomainRuleException("Unavailable room", "No consultation rooms available")); // make sure no rooms are available - _testConference.Participants[1].UpdateCurrentRoom(RoomType.ConsultationRoom1); - _testConference.Participants[4].UpdateCurrentRoom(RoomType.ConsultationRoom2); + TestConference.Participants[1].UpdateCurrentRoom(RoomType.ConsultationRoom1); + TestConference.Participants[4].UpdateCurrentRoom(RoomType.ConsultationRoom2); var answer = ConsultationAnswer.Accepted; @@ -204,21 +146,21 @@ public async Task should_return_error_when_consultation_accepted_but_no_room_is_ Answer = answer }; - var result = await _controller.HandleConsultationRequest(request); + var result = await Controller.HandleConsultationRequest(request); var typedResult = (ObjectResult) result; typedResult.StatusCode.Should().Be((int) HttpStatusCode.BadRequest); - _hubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Once); - _hubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Never); - _hubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Once); - _eventHubClientMock.Verify( + HubContextMock.Verify(x => x.Clients.Group(requestedBy.Username.ToLowerInvariant()), Times.Once); + HubContextMock.Verify(x => x.Clients.Group(requestedFor.Username.ToLowerInvariant()), Times.Never); + HubContextMock.Verify(x => x.Clients.Group(EventHub.VhOfficersGroupName), Times.Once); + EventHubClientMock.Verify( x => x.ConsultationMessage(conferenceId, requestedBy.Username, requestedFor.Username, answer.ToString()), Times.Exactly(2)); - _videoPlatformServiceMock.Verify(x => - x.StartPrivateConsultationAsync(_testConference, requestedBy, requestedFor), Times.Once); - _videoPlatformServiceMock.VerifyNoOtherCalls(); + VideoPlatformServiceMock.Verify(x => + x.StartPrivateConsultationAsync(TestConference, requestedBy, requestedFor), Times.Once); + VideoPlatformServiceMock.VerifyNoOtherCalls(); } } } \ No newline at end of file diff --git a/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/RespondToAdminConsultationRequestTests.cs b/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/RespondToAdminConsultationRequestTests.cs new file mode 100644 index 000000000..198985716 --- /dev/null +++ b/VideoAPI/VideoApi.UnitTests/Controllers/Consultation/RespondToAdminConsultationRequestTests.cs @@ -0,0 +1,58 @@ +using System.Threading.Tasks; +using Moq; +using NUnit.Framework; +using VideoApi.Contract.Requests; +using VideoApi.Domain.Enums; + +namespace VideoApi.UnitTests.Controllers.Consultation +{ + [TestFixture] + public class RespondToAdminConsultationRequestTests : ConsultationControllerTestBase + { + [Test] + public async Task should_transfer_participant_when_consultation_is_accepted() + { + var conferenceId = TestConference.Id; + var participant = TestConference.GetParticipants()[3]; + + var roomFrom = participant.CurrentRoom.Value; + var request = new AdminConsultationRequest + { + ConferenceId = conferenceId, + ParticipantId = participant.Id, + ConsultationRoom = RoomType.ConsultationRoom1, + Answer = ConsultationAnswer.Accepted + }; + + await Controller.RespondToAdminConsultationRequest(request); + + VideoPlatformServiceMock.Verify(x => + x.TransferParticipantAsync(conferenceId, participant.Id, roomFrom, request.ConsultationRoom), + Times.Once); + VideoPlatformServiceMock.VerifyNoOtherCalls(); + } + + [Test] + public async Task should_not_transfer_participant_when_consultation_is_not_accepted() + { + var conferenceId = TestConference.Id; + var participant = TestConference.GetParticipants()[3]; + + var roomFrom = participant.CurrentRoom.Value; + var request = new AdminConsultationRequest + { + ConferenceId = conferenceId, + ParticipantId = participant.Id, + ConsultationRoom = RoomType.ConsultationRoom1, + Answer = ConsultationAnswer.Rejected + }; + + await Controller.RespondToAdminConsultationRequest(request); + + VideoPlatformServiceMock.Verify(x => + x.TransferParticipantAsync(conferenceId, participant.Id, roomFrom, request.ConsultationRoom), + Times.Never); + VideoPlatformServiceMock.VerifyNoOtherCalls(); + } + } +} \ No newline at end of file diff --git a/VideoAPI/VideoApi.UnitTests/Events/EventHandlerFactoryTests.cs b/VideoAPI/VideoApi.UnitTests/Events/EventHandlerFactoryTests.cs index dff448fbe..60a09286f 100644 --- a/VideoAPI/VideoApi.UnitTests/Events/EventHandlerFactoryTests.cs +++ b/VideoAPI/VideoApi.UnitTests/Events/EventHandlerFactoryTests.cs @@ -18,6 +18,7 @@ public class EventHandlerFactoryTests : EventHandlerTestBase [TestCase(EventType.Transfer, typeof(TransferEventHandler))] [TestCase(EventType.ParticipantJoining, typeof(ParticipantJoiningEventHandler))] [TestCase(EventType.SelfTestFailed, typeof(SelfTestFailedEventHandler))] + [TestCase(EventType.VhoCall, typeof(VhOfficerCallEventHandler))] public void should_return_instance_of_event_handler_when_factory_get_is_called_with_valid_request( EventType eventType, Type typeOfEventHandler) { diff --git a/VideoAPI/VideoApi.UnitTests/Events/EventHandlerTestBase.cs b/VideoAPI/VideoApi.UnitTests/Events/EventHandlerTestBase.cs index 7d657f968..796132e36 100644 --- a/VideoAPI/VideoApi.UnitTests/Events/EventHandlerTestBase.cs +++ b/VideoAPI/VideoApi.UnitTests/Events/EventHandlerTestBase.cs @@ -58,7 +58,9 @@ public void Setup() new ParticipantJoiningEventHandler(QueryHandlerMock.Object, CommandHandlerMock.Object, ServiceBusQueueClient, EventHubContextMock.Object), new SelfTestFailedEventHandler(QueryHandlerMock.Object, CommandHandlerMock.Object, ServiceBusQueueClient, - EventHubContextMock.Object) + EventHubContextMock.Object), + new VhOfficerCallEventHandler(QueryHandlerMock.Object, CommandHandlerMock.Object, + ServiceBusQueueClient, EventHubContextMock.Object) }; TestConference = new ConferenceBuilder() diff --git a/VideoAPI/VideoApi.UnitTests/Events/JoinedEventHandlerTests.cs b/VideoAPI/VideoApi.UnitTests/Events/JoinedEventHandlerTests.cs index 6db9689d5..20baf7e2d 100644 --- a/VideoAPI/VideoApi.UnitTests/Events/JoinedEventHandlerTests.cs +++ b/VideoAPI/VideoApi.UnitTests/Events/JoinedEventHandlerTests.cs @@ -1,14 +1,12 @@ using System; using System.Linq; using System.Threading.Tasks; -using FluentAssertions; using Moq; using NUnit.Framework; using VideoApi.DAL.Commands; using VideoApi.Domain.Enums; using VideoApi.Events.Handlers; using VideoApi.Events.Models; -using VideoApi.Events.Models.Enums; namespace VideoApi.UnitTests.Events { diff --git a/VideoAPI/VideoApi.UnitTests/Events/VhOfficerCallEventHandlerTests.cs b/VideoAPI/VideoApi.UnitTests/Events/VhOfficerCallEventHandlerTests.cs new file mode 100644 index 000000000..d6a132c76 --- /dev/null +++ b/VideoAPI/VideoApi.UnitTests/Events/VhOfficerCallEventHandlerTests.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Moq; +using NUnit.Framework; +using VideoApi.Domain.Enums; +using VideoApi.Events.Handlers; +using VideoApi.Events.Models; + +namespace VideoApi.UnitTests.Events +{ + public class VhOfficerCallEventHandlerTests : EventHandlerTestBase + { + private VhOfficerCallEventHandler _eventHandler; + + [TestCase(null)] + [TestCase(RoomType.AdminRoom)] + [TestCase(RoomType.HearingRoom)] + [TestCase(RoomType.WaitingRoom)] + public void should_throw_exception_when_transfer_to_is_not_a_consultation_room(RoomType? transferTo) + { + _eventHandler = new VhOfficerCallEventHandler(QueryHandlerMock.Object, CommandHandlerMock.Object, + ServiceBusQueueClient, EventHubContextMock.Object); + + var conference = TestConference; + var participantForEvent = conference.GetParticipants().First(x => x.UserRole == UserRole.Individual); + + + var callbackEvent = new CallbackEvent + { + EventType = EventType.Transfer, + EventId = Guid.NewGuid().ToString(), + ConferenceId = conference.Id, + ParticipantId = participantForEvent.Id, + TransferTo = transferTo, + TimeStampUtc = DateTime.UtcNow + }; + + var exception = Assert.ThrowsAsync(async () => await _eventHandler.HandleAsync(callbackEvent)); + exception.Message.Should().Be("No consultation room provided"); + } + + [TestCase(RoomType.ConsultationRoom1)] + [TestCase(RoomType.ConsultationRoom2)] + public async Task should_raise_admin_consultation_message(RoomType? transferTo) + { + _eventHandler = new VhOfficerCallEventHandler(QueryHandlerMock.Object, CommandHandlerMock.Object, + ServiceBusQueueClient, EventHubContextMock.Object); + + var conference = TestConference; + var participantForEvent = conference.GetParticipants().First(x => x.UserRole == UserRole.Individual); + + + var callbackEvent = new CallbackEvent + { + EventType = EventType.Transfer, + EventId = Guid.NewGuid().ToString(), + ConferenceId = conference.Id, + ParticipantId = participantForEvent.Id, + TransferTo = transferTo, + TimeStampUtc = DateTime.UtcNow + }; + + await _eventHandler.HandleAsync(callbackEvent); + + EventHubClientMock.Verify(x => + x.AdminConsultationMessage(conference.Id, transferTo.Value, + participantForEvent.Username.ToLowerInvariant(), null), + Times.Once); + } + } +} \ No newline at end of file diff --git a/VideoAPI/VideoApi.UnitTests/Validation/AdminConsultationRequestValidationTests.cs b/VideoAPI/VideoApi.UnitTests/Validation/AdminConsultationRequestValidationTests.cs new file mode 100644 index 000000000..33a391e93 --- /dev/null +++ b/VideoAPI/VideoApi.UnitTests/Validation/AdminConsultationRequestValidationTests.cs @@ -0,0 +1,96 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using Video.API.Validations; +using VideoApi.Contract.Requests; +using VideoApi.Domain.Enums; + +namespace VideoApi.UnitTests.Validation +{ + public class AdminConsultationRequestValidationTests + { + private AdminConsultationRequestValidation _validator; + + [OneTimeSetUp] + public void OneTimeSetUp() + { + _validator = new AdminConsultationRequestValidation(); + } + + [Test] + public async Task should_pass_validation() + { + var request = BuildRequest(); + + var result = await _validator.ValidateAsync(request); + + result.IsValid.Should().BeTrue(); + } + + [Test] + public async Task should_fail_validation_when_conference_is_empty() + { + var request = BuildRequest(); + request.ConferenceId = Guid.Empty; + + var result = await _validator.ValidateAsync(request); + + result.IsValid.Should().BeFalse(); + result.Errors.Any(x => x.ErrorMessage == AdminConsultationRequestValidation.NoConferenceIdErrorMessage) + .Should().BeTrue(); + } + + [Test] + public async Task should_fail_validation_when_participant_id_is_empty() + { + var request = BuildRequest(); + request.ParticipantId = Guid.Empty; + + var result = await _validator.ValidateAsync(request); + + result.IsValid.Should().BeFalse(); + result.Errors.Any(x => x.ErrorMessage == AdminConsultationRequestValidation.NoParticipantIdErrorMessage) + .Should().BeTrue(); + } + + [Test] + public async Task should_fail_validation_when_answer_is_invalid() + { + var request = BuildRequest(); + request.Answer = ConsultationAnswer.None; + + var result = await _validator.ValidateAsync(request); + + result.IsValid.Should().BeFalse(); + result.Errors.Any(x => x.ErrorMessage == AdminConsultationRequestValidation.NoAnswerErrorMessage) + .Should().BeTrue(); + } + + [Test] + public async Task should_fail_validation_when_room_is_not_consultation() + { + var request = BuildRequest(); + request.ConsultationRoom = RoomType.AdminRoom; + + var result = await _validator.ValidateAsync(request); + + result.IsValid.Should().BeFalse(); + result.Errors.Any(x => x.ErrorMessage == AdminConsultationRequestValidation.NotValidConsultationRoomMessage) + .Should().BeTrue(); + } + + private AdminConsultationRequest BuildRequest() + { + var request = Builder.CreateNew() + .With(x => x.ConferenceId = Guid.NewGuid()) + .With(x => x.ParticipantId = Guid.NewGuid()) + .With(x => x.Answer = ConsultationAnswer.Accepted) + .With(x => x.ConsultationRoom = RoomType.ConsultationRoom1) + .Build(); + return request; + } + } +} \ No newline at end of file