Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VIH-11041 detect if any participant of endpoint are screened when inviting to a new or existing consultation #2273

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 72 additions & 7 deletions VideoWeb/VideoWeb.Common/Models/Conference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,6 @@ public void AddParticipant(Participant participant)
Participants.Add(participant);
}
}

public void RemoveParticipant(Guid referenceId)
{
Participants.RemoveAll(x => x.RefId == referenceId);
}

public bool IsParticipantInConference(string username)
{
Expand Down Expand Up @@ -164,11 +159,79 @@ private CivilianRoom GetOrCreateCivilianRoom(long roomId)
return room;
}

public CivilianRoom GetRoom(Guid participantId)
/// <summary>
/// Check if any of the participants or endpoints from the given list are screened from each other.
/// If any of the externalReferenceIds for an entity exist in the protectFrom list of another entity, they
/// cannot be in a consultation together.
/// </summary>
/// <param name="participantIds">List of participants to be in a consultation</param>
/// <param name="endpointIds">List of endpoints to be in a consultation</param>
public bool AreEntitiesScreenedFromEachOther(List<Guid> participantIds, List<Guid> endpointIds)
{
var allExternalReferenceIds = Participants.Select(x => x.ExternalReferenceId)
.Union(Endpoints.Select(x => x.ExternalReferenceId)).ToList();

foreach (var participantId in participantIds)
{
var participant = Participants.Find(x => x.Id == participantId);
if (participant == null) continue;
if (participant.ProtectFrom.Exists(allExternalReferenceIds.Contains)) return true;
}

foreach (var endpointId in endpointIds)
{
var endpoint = Endpoints.Find(x => x.Id == endpointId);
if (endpoint == null) continue;
if (endpoint.ProtectFrom.Exists(allExternalReferenceIds.Contains)) return true;
}

return false;
}

/// <summary>
/// Check if the participant is screened from any of the entities in a existing consultation room
/// </summary>
/// <param name="roomLabel">The label of the consultation room</param>
/// <param name="participantId">The id of the participant attempting to join a consultation room</param>
/// <returns>true if the participant is not screened from any of the entities in the room</returns>
public bool CanParticipantJoinConsultationRoom(string roomLabel, Guid participantId)
{
var participant = Participants.Find(x => x.Id == participantId);
if (participant == null)
throw new ArgumentException($"The participant {participantId} does not exist", nameof(participantId));

var ids = GetParticipantsAndEndpointsInRoom(roomLabel);
ids.participantIds.Add(participantId);

return !AreEntitiesScreenedFromEachOther(ids.participantIds, ids.endpointIds);
}

/// <summary>
/// Check if the endpoint is screened from any of the entities in a existing consultation room
/// </summary>
/// <param name="roomLabel">The label of the consultation room</param>
/// <param name="endpointId">The id of the endpoint attempting to join a consultation room</param>
/// <returns>true if the endpoint is not screened from any of the entities in the room</returns>
public bool CanEndpointJoinConsultationRoom(string roomLabel, Guid endpointId)
{
var endpoint = Endpoints.Find(x => x.Id == endpointId);
if (endpoint == null)
throw new ArgumentException($"The endpoint {endpointId} does not exist", nameof(endpointId));

var ids = GetParticipantsAndEndpointsInRoom(roomLabel);
ids.endpointIds.Add(endpointId);

return !AreEntitiesScreenedFromEachOther(ids.participantIds, ids.endpointIds);
}

private (List<Guid> participantIds, List<Guid> endpointIds) GetParticipantsAndEndpointsInRoom(string roomLabel)
{
return CivilianRooms.Find(room => room.Participants.Contains(participantId));
var participants = Participants.Where(x => x.CurrentRoom?.Label == roomLabel).Select(x => x.Id).ToList();
var endpoints = Endpoints.Where(x => x.CurrentRoom?.Label == roomLabel).Select(x => x.Id).ToList();
return (participants, endpoints);
}


public HearingLayout GetRecommendedLayout()
{
var numOfParticipantsIncJudge = Participants.Count + Endpoints.Count;
Expand Down Expand Up @@ -248,5 +311,7 @@ private List<Endpoint> GetNonScreenedEndpoints()

return endpoints;
}


}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ namespace VideoWeb.Contract.Request
{
public class StartPrivateConsultationRequest
{
public Guid[] InviteParticipants { get; set; }
public Guid[] InviteParticipants { get; set; } = [];

public Guid[] InviteEndpoints { get; set; }
public Guid[] InviteEndpoints { get; set; } = [];

public Guid ConferenceId { get; set; }

Expand Down
10 changes: 10 additions & 0 deletions VideoWeb/VideoWeb.Contract/Responses/ParticipantResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,15 @@ public class ParticipantResponse
/// List of participants linked this participant
/// </summary>
public List<LinkedParticipantResponse> LinkedParticipants { get; set; }

/// <summary>
/// A unique identifier for the participant (used by special measures)
/// </summary>
public string ExternalReferenceId { get; set; }

/// <summary>
/// List of external references to protect this participant from
/// </summary>
public List<string> ProtectFrom { get; set; }
}
}
11 changes: 11 additions & 0 deletions VideoWeb/VideoWeb.Contract/Responses/VideoEndpointResponse.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using VideoWeb.Common.Models;

namespace VideoWeb.Contract.Responses;
Expand Down Expand Up @@ -41,4 +42,14 @@ public class VideoEndpointResponse
/// The endpoint's interpreter language, if applicable
/// </summary>
public InterpreterLanguageResponse InterpreterLanguage { get; set; }

/// <summary>
/// A unique identifier for the participant (used by special measures)
/// </summary>
public string ExternalReferenceId { get; set; }

/// <summary>
/// List of external references to protect this participant from
/// </summary>
public List<string> ProtectFrom { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,35 @@ public ConferenceCacheModelBuilder()
Builder<Participant>.CreateNew().With(x => x.Role = Role.Individual)
.With(x => x.Username = Faker.Internet.Email())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString())
.With(x => x.Id = Guid.NewGuid()).Build(),
.With(x => x.Id = Guid.NewGuid())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder<Participant>.CreateNew().With(x => x.Role = Role.Representative)
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString())
.With(x => x.Username = Faker.Internet.Email())
.With(x => x.Id = Guid.NewGuid()).Build(),
.With(x => x.Id = Guid.NewGuid())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder<Participant>.CreateNew().With(x => x.Role = Role.Individual)
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString())
.With(x => x.Username = Faker.Internet.Email())
.With(x => x.Id = Guid.NewGuid()).Build(),
.With(x => x.Id = Guid.NewGuid())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder<Participant>.CreateNew().With(x => x.Role = Role.Representative)
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString())
.With(x => x.Username = Faker.Internet.Email())
.With(x => x.Id = Guid.NewGuid()).Build()
.With(x => x.Id = Guid.NewGuid())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build()
},
Endpoints = new List<Endpoint>
{
Builder<Endpoint>.CreateNew().With(x => x.Id = Guid.NewGuid())
Builder<Endpoint>.CreateNew()
.With(x => x.Id = Guid.NewGuid())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).With(x => x.DisplayName = "EP1")
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString())
.Build(),
Builder<Endpoint>.CreateNew().With(x => x.Id = Guid.NewGuid())
Builder<Endpoint>.CreateNew()
.With(x => x.Id = Guid.NewGuid())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).With(x => x.DisplayName = "EP2")
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString())
.Build()
},
HearingVenueName = "Hearing Venue Test"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task should_return_accepted()
var request = new AddEndpointConsultationRequest
{
ConferenceId = _testConference.Id,
EndpointId = Guid.NewGuid(),
EndpointId = _testConference.Endpoints[0].Id,
RoomLabel = "RoomLabel"
};

Expand Down Expand Up @@ -81,6 +81,37 @@ public async Task should_return_unauthorised_not_in_conference()
result.Should().BeOfType<UnauthorizedObjectResult>();
}

[Test]
public async Task should_return_badrequest_when_endpoint_is_screened()
{
// arrange
var roomLabel = "RoomLabel1";
var individual = _testConference.Participants.Find(x => x.Role == Role.Individual);
var endpoint = _testConference.Endpoints[0];
individual.ProtectFrom.Add(endpoint.ExternalReferenceId);
var rep = _testConference.Participants.Find(x => x.Role == Role.Representative);
individual.CurrentRoom = new ConsultationRoom {Label = roomLabel};
rep.CurrentRoom = new ConsultationRoom {Label = roomLabel};

SetupControllerWithClaims(rep.Username);
var request = new AddEndpointConsultationRequest
{
ConferenceId = _testConference.Id,
EndpointId = endpoint.Id,
RoomLabel = roomLabel
};

// act
var result = await _sut.AddEndpointToConsultationAsync(request, CancellationToken.None);

// assert
result.Should().BeOfType<BadRequestObjectResult>().Which.Value.Should()
.Be(ConsultationsController.ConsultationHasScreenedEndpointErrorMessage);

_mocker.Mock<IVideoApiClient>().Verify(
x => x.JoinEndpointToConsultationAsync(It.IsAny<EndpointConsultationRequest>(), It.IsAny<CancellationToken>()), Times.Never);
}

[Test]
public async Task should_return_call_JoinEndpointToConsultationAsync_with_correct_params()
{
Expand All @@ -89,7 +120,7 @@ public async Task should_return_call_JoinEndpointToConsultationAsync_with_correc
var request = new AddEndpointConsultationRequest
{
ConferenceId = _testConference.Id,
EndpointId = Guid.NewGuid(),
EndpointId = _testConference.Endpoints[0].Id,
RoomLabel = "RoomLabel"
};

Expand All @@ -115,7 +146,7 @@ public async Task should_return_call_videoapi_response_error_joining()
var request = new AddEndpointConsultationRequest
{
ConferenceId = _testConference.Id,
EndpointId = Guid.NewGuid(),
EndpointId = _testConference.Endpoints[0].Id,
RoomLabel = "RoomLabel"
};
var apiException = new VideoApiException<ProblemDetails>("Bad Request", (int) HttpStatusCode.BadRequest,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@ public static Conference BuildConferenceForTest()
.With(x => x.Username = "[email protected]")
.Build(),
Builder<Participant>.CreateNew().With(x => x.Role = Role.Individual)
.With(x => x.Id = Guid.NewGuid()).With(x => x.Username = "[email protected]").Build(),
.With(x => x.Id = Guid.NewGuid()).With(x => x.Username = "[email protected]")
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder<Participant>.CreateNew().With(x => x.Role = Role.Representative)
.With(x=> x.Username = "[email protected]")
.With(x => x.Id = Guid.NewGuid()).Build(),
.With(x => x.Id = Guid.NewGuid())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder<Participant>.CreateNew().With(x => x.Role = Role.Individual)
.With(x => x.Id = Guid.NewGuid()).Build(),
.With(x => x.Id = Guid.NewGuid())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder<Participant>.CreateNew().With(x => x.Role = Role.Representative)
.With(x => x.Id = Guid.NewGuid()).Build(),
.With(x => x.Id = Guid.NewGuid())
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder<Participant>.CreateNew()
.With(x => x.Role = Role.StaffMember).With(x => x.Id = Guid.NewGuid())
.With(x => x.Username = "[email protected]")
Expand All @@ -39,11 +43,14 @@ public static Conference BuildConferenceForTest()
Endpoints = new List<Endpoint>
{
Builder<Endpoint>.CreateNew().With(x => x.Id = Guid.NewGuid()).With(x => x.DisplayName = "EP1")
.With(x=> x.DefenceAdvocateUsername = "[email protected]").Build(),
.With(x=> x.DefenceAdvocateUsername = "[email protected]")
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder<Endpoint>.CreateNew().With(x => x.Id = Guid.NewGuid()).With(x => x.DisplayName = "EP2")
.With(x=> x.DefenceAdvocateUsername = "[email protected]").Build(),
.With(x=> x.DefenceAdvocateUsername = "[email protected]")
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
Builder<Endpoint>.CreateNew().With(x => x.Id = Guid.NewGuid()).With(x => x.DisplayName = "EP3")
.With(x=> x.DefenceAdvocateUsername = "[email protected]").Build(),
.With(x=> x.DefenceAdvocateUsername = "[email protected]")
.With(x => x.ExternalReferenceId = Guid.NewGuid().ToString()).Build(),
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -96,11 +97,12 @@ public async Task should_return_accepted_if_user_is_in_consultation()
var cp = new ClaimsPrincipalBuilder().WithRole(AppRoles.RepresentativeRole)
.WithUsername("[email protected]").Build();
_sut = SetupControllerWithClaims(cp);
var participant = _testConference.Participants.First(x => x.Role == Role.Individual);

var request = new InviteToConsultationRequest
{
ConferenceId = _testConference.Id,
ParticipantId = _testConference.Participants[0].Id,
ParticipantId = participant.Id,
RoomLabel = "Room1"
};

Expand All @@ -112,7 +114,43 @@ public async Task should_return_accepted_if_user_is_in_consultation()
_mocker.Mock<IConsultationNotifier>()
.Verify(
x => x.NotifyConsultationRequestAsync(_testConference, "Room1", _testConference.Participants[2].Id,
_testConference.Participants[0].Id), Times.Once);
participant.Id), Times.Once);
}

[Test]
public async Task should_return_badrequest_if_invitee_is_screened_from_existing_participant_in_room()
{
// arrange
var cp = new ClaimsPrincipalBuilder().WithRole(AppRoles.RepresentativeRole)
.WithUsername("[email protected]").Build();
_sut = SetupControllerWithClaims(cp);

var roomLabel = "RoomLabel1";
var individual = _testConference.Participants.Find(x => x.Role == Role.Individual);
var endpoint = _testConference.Endpoints[0];
individual.ProtectFrom.Add(endpoint.ExternalReferenceId);
var rep = _testConference.Participants.Find(x => x.Role == Role.Representative);

rep.CurrentRoom = new ConsultationRoom {Label = roomLabel};
rep.Username = "[email protected]";
endpoint.CurrentRoom = new ConsultationRoom {Label = roomLabel};

var request = new InviteToConsultationRequest
{
ConferenceId = _testConference.Id,
RoomLabel = roomLabel,
ParticipantId = individual.Id
};

// act
var result = await _sut.InviteToConsultationAsync(request, CancellationToken.None);

// assert
result.Should().BeOfType<BadRequestObjectResult>().Which.Value.Should()
.Be(ConsultationsController.ConsultationHasScreenedParticipantErrorMessage);

_mocker.Mock<IConsultationNotifier>().Verify(
x => x.NotifyConsultationRequestAsync(_testConference, roomLabel, Guid.Empty, individual.Id), Times.Never);
}

private ConsultationsController SetupControllerWithClaims(ClaimsPrincipal claimsPrincipal)
Expand Down
Loading
Loading