repo created

This commit is contained in:
Fr4nz D13trich 2025-09-18 18:11:17 +02:00
commit 93184d21d1
1403 changed files with 189511 additions and 0 deletions

View file

@ -0,0 +1,654 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.DISCONNECTED;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.IN_CALL;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_AUDIO;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_VIDEO;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.GUEST;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.GUEST_MODERATOR;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.MODERATOR;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.OWNER;
import static com.nextcloud.talk.models.json.participants.Participant.ParticipantType.USER;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
public class CallParticipantListExternalSignalingTest {
private final ParticipantsUpdateParticipantBuilder builder = new ParticipantsUpdateParticipantBuilder();
private CallParticipantList callParticipantList;
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener;
private CallParticipantList.Observer mockedCallParticipantListObserver;
private Collection<Participant> expectedJoined;
private Collection<Participant> expectedUpdated;
private Collection<Participant> expectedLeft;
private Collection<Participant> expectedUnchanged;
// The order of the left participants in some tests depends on how they are internally sorted by the map, so the
// list of left participants needs to be checked ignoring the sorting (or, rather, sorting by session ID as in
// expectedLeft).
// Other tests can just relay on the not guaranteed, but known internal sorting of the elements.
private final ArgumentMatcher<List<Participant>> matchesExpectedLeftIgnoringOrder = left -> {
Collections.sort(left, Comparator.comparing(Participant::getSessionId));
return expectedLeft.equals(left);
};
private static class ParticipantsUpdateParticipantBuilder {
private Participant newUser(long inCall, long lastPing, String sessionId, Participant.ParticipantType type,
String userId) {
Participant participant = new Participant();
participant.setInCall(inCall);
participant.setLastPing(lastPing);
participant.setSessionId(sessionId);
participant.setType(type);
participant.setUserId(userId);
participant.setActorType(Participant.ActorType.USERS);
participant.setActorId(userId);
return participant;
}
private Participant newGuest(long inCall, long lastPing, String sessionId, Participant.ParticipantType type) {
Participant participant = new Participant();
participant.setInCall(inCall);
participant.setLastPing(lastPing);
participant.setSessionId(sessionId);
participant.setType(type);
participant.setActorType(Participant.ActorType.GUESTS);
participant.setActorId("sha1-" + sessionId);
return participant;
}
}
@Before
public void setUp() {
SignalingMessageReceiver mockedSignalingMessageReceiver = mock(SignalingMessageReceiver.class);
callParticipantList = new CallParticipantList(mockedSignalingMessageReceiver);
mockedCallParticipantListObserver = mock(CallParticipantList.Observer.class);
// Get internal ParticipantListMessageListener from callParticipantList set in the
// mockedSignalingMessageReceiver.
ArgumentCaptor<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListenerArgumentCaptor =
ArgumentCaptor.forClass(SignalingMessageReceiver.ParticipantListMessageListener.class);
verify(mockedSignalingMessageReceiver).addListener(participantListMessageListenerArgumentCaptor.capture());
participantListMessageListener = participantListMessageListenerArgumentCaptor.getValue();
expectedJoined = new ArrayList<>();
expectedUpdated = new ArrayList<>();
expectedLeft = new ArrayList<>();
expectedUnchanged = new ArrayList<>();
}
@Test
public void testParticipantsUpdateJoinRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateJoinRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", USER, "theUserId4"));
participants.add(builder.newUser(DISCONNECTED, 5, "theSessionId5", OWNER, "theUserId5"));
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateJoinRoomThenJoinCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateJoinRoomThenJoinCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateJoinRoomAndCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateJoinRoomAndCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateJoinRoomAndCallRepeated() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
participantListMessageListener.onParticipantsUpdate(participants);
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateChangeCallFlags() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateChangeCallFlagsSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", USER, "theUserId4"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
expectedUpdated.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedUpdated.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", USER, "theUserId4"));
expectedUnchanged.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2", GUEST));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateChangeLastPing() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", MODERATOR, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateChangeLastPingSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", USER, "theUserId3"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 108, "theSessionId2", GUEST));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 815, "theSessionId3", USER, "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateChangeParticipantType() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", USER, "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateChangeParticipantTypeeSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", USER, "theUserId3"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", USER, "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2", GUEST_MODERATOR));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", MODERATOR, "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateLeaveCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateLeaveCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateLeaveCallThenLeaveRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateLeaveCallThenLeaveRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testParticipantsUpdateLeaveCallAndRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participantListMessageListener.onParticipantsUpdate(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testParticipantsUpdateLeaveCallAndRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
}
@Test
public void testParticipantsUpdateSeveralEventsSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participants.add(builder.newUser(IN_CALL, 5, "theSessionId5", OWNER, "theUserId5"));
// theSessionId6 has not joined yet.
participants.add(builder.newGuest(IN_CALL | WITH_VIDEO, 7, "theSessionId7", GUEST));
participants.add(builder.newUser(DISCONNECTED, 8, "theSessionId8", USER, "theUserId8"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 9, "theSessionId9", MODERATOR, "theUserId9"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
// theSessionId1 is gone.
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", OWNER, "theUserId5"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6", GUEST));
participants.add(builder.newGuest(IN_CALL, 7, "theSessionId7", GUEST));
participants.add(builder.newUser(IN_CALL, 8, "theSessionId8", USER, "theUserId8"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", USER, "theUserId9"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6", GUEST));
expectedJoined.add(builder.newUser(IN_CALL, 8, "theSessionId8", USER, "theUserId8"));
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", OWNER, "theUserId5"));
expectedUpdated.add(builder.newGuest(IN_CALL, 7, "theSessionId7", GUEST));
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2", GUEST));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", USER, "theUserId4"));
// Last ping and participant type are not seen as changed, even if they did.
expectedUnchanged.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", USER, "theUserId9"));
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
}
@Test
public void testAllParticipantsUpdateDisconnected() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
InOrder inOrder = inOrder(mockedCallParticipantListObserver);
inOrder.verify(mockedCallParticipantListObserver).onCallEndedForAll();
inOrder.verify(mockedCallParticipantListObserver).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testAllParticipantsUpdateDisconnectedWithSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participants.add(builder.newUser(DISCONNECTED, 2, "theSessionId2", USER, "theUserId2"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", USER, "theUserId3"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 4, "theSessionId4", GUEST));
participantListMessageListener.onParticipantsUpdate(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", MODERATOR, "theUserId1"));
expectedLeft.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", USER, "theUserId3"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 4, "theSessionId4", GUEST));
InOrder inOrder = inOrder(mockedCallParticipantListObserver);
inOrder.verify(mockedCallParticipantListObserver).onCallEndedForAll();
inOrder.verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
}
@Test
public void testAllParticipantsUpdateDisconnectedNoOneInCall() {
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
InOrder inOrder = inOrder(mockedCallParticipantListObserver);
inOrder.verify(mockedCallParticipantListObserver).onCallEndedForAll();
verifyNoMoreInteractions(mockedCallParticipantListObserver);
}
@Test
public void testAllParticipantsUpdateDisconnectedThenJoinCallAgain() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
participantListMessageListener.onAllParticipantsUpdate(DISCONNECTED);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
participantListMessageListener.onParticipantsUpdate(participants);
expectedJoined.add(builder.newUser(IN_CALL, 1, "theSessionId1", MODERATOR, "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
}

View file

@ -0,0 +1,528 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.DISCONNECTED;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.IN_CALL;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_AUDIO;
import static com.nextcloud.talk.models.json.participants.Participant.InCallFlags.WITH_VIDEO;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
public class CallParticipantListInternalSignalingTest {
private final UsersInRoomParticipantBuilder builder = new UsersInRoomParticipantBuilder();
private CallParticipantList callParticipantList;
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener;
private CallParticipantList.Observer mockedCallParticipantListObserver;
private Collection<Participant> expectedJoined;
private Collection<Participant> expectedUpdated;
private Collection<Participant> expectedLeft;
private Collection<Participant> expectedUnchanged;
// The order of the left participants in some tests depends on how they are internally sorted by the map, so the
// list of left participants needs to be checked ignoring the sorting (or, rather, sorting by session ID as in
// expectedLeft).
// Other tests can just relay on the not guaranteed, but known internal sorting of the elements.
private final ArgumentMatcher<List<Participant>> matchesExpectedLeftIgnoringOrder = left -> {
Collections.sort(left, Comparator.comparing(Participant::getSessionId));
return expectedLeft.equals(left);
};
private static class UsersInRoomParticipantBuilder {
private Participant newUser(long inCall, long lastPing, String sessionId, String userId) {
Participant participant = new Participant();
participant.setInCall(inCall);
participant.setLastPing(lastPing);
participant.setSessionId(sessionId);
participant.setUserId(userId);
participant.setActorType(Participant.ActorType.USERS);
participant.setActorId(userId);
return participant;
}
private Participant newGuest(long inCall, long lastPing, String sessionId) {
Participant participant = new Participant();
participant.setInCall(inCall);
participant.setLastPing(lastPing);
participant.setSessionId(sessionId);
participant.setActorType(Participant.ActorType.GUESTS);
participant.setActorId("sha1-" + sessionId);
return participant;
}
}
@Before
public void setUp() {
SignalingMessageReceiver mockedSignalingMessageReceiver = mock(SignalingMessageReceiver.class);
callParticipantList = new CallParticipantList(mockedSignalingMessageReceiver);
mockedCallParticipantListObserver = mock(CallParticipantList.Observer.class);
// Get internal ParticipantListMessageListener from callParticipantList set in the
// mockedSignalingMessageReceiver.
ArgumentCaptor<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListenerArgumentCaptor =
ArgumentCaptor.forClass(SignalingMessageReceiver.ParticipantListMessageListener.class);
verify(mockedSignalingMessageReceiver).addListener(participantListMessageListenerArgumentCaptor.capture());
participantListMessageListener = participantListMessageListenerArgumentCaptor.getValue();
expectedJoined = new ArrayList<>();
expectedUpdated = new ArrayList<>();
expectedLeft = new ArrayList<>();
expectedUnchanged = new ArrayList<>();
}
@Test
public void testUsersInRoomJoinRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomJoinRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", "theUserId4"));
participants.add(builder.newUser(DISCONNECTED, 5, "theSessionId5", "theUserId5"));
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomJoinRoomThenJoinCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomJoinRoomThenJoinCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(DISCONNECTED, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomJoinRoomAndCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomJoinRoomAndCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
expectedJoined.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomJoinRoomAndCallRepeated() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
participantListMessageListener.onUsersInRoom(participants);
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomChangeCallFlags() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomChangeCallFlagsSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", "theUserId4"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
expectedUpdated.add(builder.newUser(IN_CALL, 1, "theSessionId1", "theUserId1"));
expectedUpdated.add(builder.newUser(IN_CALL | WITH_VIDEO, 4, "theSessionId4", "theUserId4"));
expectedUnchanged.add(builder.newGuest(IN_CALL | WITH_AUDIO | WITH_VIDEO, 2, "theSessionId2"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomChangeLastPing() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", "theUserId1"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomChangeLastPingSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 2, "theSessionId2"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 3, "theSessionId3", "theUserId3"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 108, "theSessionId2"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 815, "theSessionId3", "theUserId3"));
callParticipantList.addObserver(mockedCallParticipantListObserver);
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomLeaveCall() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomLeaveCallSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomLeaveCallThenLeaveRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomLeaveCallThenLeaveRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
verifyNoInteractions(mockedCallParticipantListObserver);
}
@Test
public void testUsersInRoomLeaveCallAndRoom() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participantListMessageListener.onUsersInRoom(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
verify(mockedCallParticipantListObserver, only()).onCallParticipantsChanged(expectedJoined, expectedUpdated,
expectedLeft, expectedUnchanged);
}
@Test
public void testUsersInRoomLeaveCallAndRoomSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participantListMessageListener.onUsersInRoom(participants);
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined), eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder), eq(expectedUnchanged));
}
@Test
public void testUsersInRoomSeveralEventsSeveralParticipants() {
List<Participant> participants = new ArrayList<>();
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 1, "theSessionId1", "theUserId1"));
participants.add(builder.newGuest(IN_CALL, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participants.add(builder.newUser(IN_CALL, 5, "theSessionId5", "theUserId5"));
// theSessionId6 has not joined yet.
participants.add(builder.newGuest(IN_CALL | WITH_VIDEO, 7, "theSessionId7"));
participants.add(builder.newUser(DISCONNECTED, 8, "theSessionId8", "theUserId8"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 9, "theSessionId9", "theUserId9"));
participantListMessageListener.onUsersInRoom(participants);
callParticipantList.addObserver(mockedCallParticipantListObserver);
participants = new ArrayList<>();
// theSessionId1 is gone.
participants.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
participants.add(builder.newUser(DISCONNECTED, 3, "theSessionId3", "theUserId3"));
participants.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", "theUserId5"));
participants.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6"));
participants.add(builder.newGuest(IN_CALL, 7, "theSessionId7"));
participants.add(builder.newUser(IN_CALL, 8, "theSessionId8", "theUserId8"));
participants.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", "theUserId9"));
participantListMessageListener.onUsersInRoom(participants);
expectedJoined.add(builder.newGuest(IN_CALL | WITH_AUDIO, 6, "theSessionId6"));
expectedJoined.add(builder.newUser(IN_CALL, 8, "theSessionId8", "theUserId8"));
expectedUpdated.add(builder.newUser(IN_CALL | WITH_AUDIO | WITH_VIDEO, 5, "theSessionId5", "theUserId5"));
expectedUpdated.add(builder.newGuest(IN_CALL, 7, "theSessionId7"));
expectedLeft.add(builder.newUser(DISCONNECTED, 1, "theSessionId1", "theUserId1"));
expectedLeft.add(builder.newGuest(DISCONNECTED, 2, "theSessionId2"));
expectedUnchanged.add(builder.newUser(IN_CALL, 4, "theSessionId4", "theUserId4"));
// Last ping is not seen as changed, even if it did.
expectedUnchanged.add(builder.newUser(IN_CALL | WITH_AUDIO, 42, "theSessionId9", "theUserId9"));
verify(mockedCallParticipantListObserver).onCallParticipantsChanged(eq(expectedJoined),
eq(expectedUpdated),
argThat(matchesExpectedLeftIgnoringOrder),
eq(expectedUnchanged));
}
}

View file

@ -0,0 +1,48 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call;
import com.nextcloud.talk.signaling.SignalingMessageReceiver;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
public class CallParticipantListTest {
private SignalingMessageReceiver mockedSignalingMessageReceiver;
private CallParticipantList callParticipantList;
private SignalingMessageReceiver.ParticipantListMessageListener participantListMessageListener;
@Before
public void setUp() {
mockedSignalingMessageReceiver = mock(SignalingMessageReceiver.class);
callParticipantList = new CallParticipantList(mockedSignalingMessageReceiver);
// Get internal ParticipantListMessageListener from callParticipantList set in the
// mockedSignalingMessageReceiver.
ArgumentCaptor<SignalingMessageReceiver.ParticipantListMessageListener> participantListMessageListenerArgumentCaptor =
ArgumentCaptor.forClass(SignalingMessageReceiver.ParticipantListMessageListener.class);
verify(mockedSignalingMessageReceiver).addListener(participantListMessageListenerArgumentCaptor.capture());
participantListMessageListener = participantListMessageListenerArgumentCaptor.getValue();
}
@Test
public void testDestroy() {
callParticipantList.destroy();
verify(mockedSignalingMessageReceiver).removeListener(participantListMessageListener);
verifyNoMoreInteractions(mockedSignalingMessageReceiver);
}
}

View file

@ -0,0 +1,52 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
class CallParticipantModelTest {
private var callParticipantModel: MutableCallParticipantModel? = null
private var mockedCallParticipantModelObserver: CallParticipantModel.Observer? = null
@Before
fun setUp() {
callParticipantModel = MutableCallParticipantModel("theSessionId")
mockedCallParticipantModelObserver = Mockito.mock(CallParticipantModel.Observer::class.java)
}
@Test
fun testSetRaisedHand() {
callParticipantModel!!.addObserver(mockedCallParticipantModelObserver)
callParticipantModel!!.setRaisedHand(true, 4815162342L)
Mockito.verify(mockedCallParticipantModelObserver, Mockito.only())?.onChange()
}
@Test
fun testSetRaisedHandTwice() {
callParticipantModel!!.addObserver(mockedCallParticipantModelObserver)
callParticipantModel!!.setRaisedHand(true, 4815162342L)
callParticipantModel!!.setRaisedHand(false, 4815162342108L)
Mockito.verify(mockedCallParticipantModelObserver, Mockito.times(2))?.onChange()
}
@Test
fun testSetRaisedHandTwiceWithSameValue() {
callParticipantModel!!.addObserver(mockedCallParticipantModelObserver)
callParticipantModel!!.setRaisedHand(true, 4815162342L)
callParticipantModel!!.setRaisedHand(true, 4815162342L)
Mockito.verify(mockedCallParticipantModelObserver, Mockito.only())?.onChange()
}
@Test
fun testEmitReaction() {
callParticipantModel!!.addObserver(mockedCallParticipantModelObserver)
callParticipantModel!!.emitReaction("theReaction")
Mockito.verify(mockedCallParticipantModelObserver, Mockito.only())?.onReaction("theReaction")
}
}

View file

@ -0,0 +1,168 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
class LocalCallParticipantModelTest {
private var localCallParticipantModel: MutableLocalCallParticipantModel? = null
private var mockedLocalCallParticipantModelObserver: LocalCallParticipantModel.Observer? = null
@Before
fun setUp() {
localCallParticipantModel = MutableLocalCallParticipantModel()
mockedLocalCallParticipantModelObserver = Mockito.mock(LocalCallParticipantModel.Observer::class.java)
}
@Test
fun testSetAudioEnabled() {
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isAudioEnabled = true
assertTrue(localCallParticipantModel!!.isAudioEnabled)
assertFalse(localCallParticipantModel!!.isSpeaking)
assertFalse(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.only())?.onChange()
}
@Test
fun testSetAudioEnabledWhileSpeakingWhileMuted() {
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isAudioEnabled = true
assertTrue(localCallParticipantModel!!.isAudioEnabled)
assertTrue(localCallParticipantModel!!.isSpeaking)
assertFalse(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.times(3))?.onChange()
}
@Test
fun testSetAudioEnabledTwiceWhileSpeakingWhileMuted() {
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isAudioEnabled = true
assertTrue(localCallParticipantModel!!.isAudioEnabled)
assertTrue(localCallParticipantModel!!.isSpeaking)
assertFalse(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.times(3))?.onChange()
}
@Test
fun testSetAudioDisabled() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isAudioEnabled = false
assertFalse(localCallParticipantModel!!.isAudioEnabled)
assertFalse(localCallParticipantModel!!.isSpeaking)
assertFalse(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.only())?.onChange()
}
@Test
fun testSetAudioDisabledWhileSpeaking() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isAudioEnabled = false
assertFalse(localCallParticipantModel!!.isAudioEnabled)
assertFalse(localCallParticipantModel!!.isSpeaking)
assertTrue(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.times(3))?.onChange()
}
@Test
fun testSetAudioDisabledTwiceWhileSpeaking() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isAudioEnabled = false
localCallParticipantModel!!.isAudioEnabled = false
assertFalse(localCallParticipantModel!!.isAudioEnabled)
assertFalse(localCallParticipantModel!!.isSpeaking)
assertTrue(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.times(3))?.onChange()
}
@Test
fun testSetSpeakingWhileAudioEnabled() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isSpeaking = true
assertTrue(localCallParticipantModel!!.isAudioEnabled)
assertTrue(localCallParticipantModel!!.isSpeaking)
assertFalse(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.only())?.onChange()
}
@Test
fun testSetNotSpeakingWhileAudioEnabled() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isSpeaking = false
assertTrue(localCallParticipantModel!!.isAudioEnabled)
assertFalse(localCallParticipantModel!!.isSpeaking)
assertFalse(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.only())?.onChange()
}
@Test
fun testSetSpeakingWhileAudioDisabled() {
localCallParticipantModel!!.isAudioEnabled = false
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isSpeaking = true
assertFalse(localCallParticipantModel!!.isAudioEnabled)
assertFalse(localCallParticipantModel!!.isSpeaking)
assertTrue(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.only())?.onChange()
}
@Test
fun testSetNotSpeakingWhileAudioDisabled() {
localCallParticipantModel!!.isAudioEnabled = false
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.addObserver(mockedLocalCallParticipantModelObserver)
localCallParticipantModel!!.isSpeaking = false
assertFalse(localCallParticipantModel!!.isAudioEnabled)
assertFalse(localCallParticipantModel!!.isSpeaking)
assertFalse(localCallParticipantModel!!.isSpeakingWhileMuted)
Mockito.verify(mockedLocalCallParticipantModelObserver, Mockito.only())?.onChange()
}
}

View file

@ -0,0 +1,641 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.models.json.signaling.NCMessagePayload
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.TestScheduler
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.times
import java.util.concurrent.TimeUnit
@Suppress("LongMethod")
class LocalStateBroadcasterMcuTest {
private var localCallParticipantModel: MutableLocalCallParticipantModel? = null
private var mockedMessageSender: MessageSender? = null
private var localStateBroadcasterMcu: LocalStateBroadcasterMcu? = null
@Before
fun setUp() {
localCallParticipantModel = MutableLocalCallParticipantModel()
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.isVideoEnabled = true
mockedMessageSender = Mockito.mock(MessageSender::class.java)
}
private fun getExpectedUnmuteAudio(): NCSignalingMessage {
val expectedUnmuteAudio = NCSignalingMessage()
expectedUnmuteAudio.roomType = "video"
expectedUnmuteAudio.type = "unmute"
val payload = NCMessagePayload()
payload.name = "audio"
expectedUnmuteAudio.payload = payload
return expectedUnmuteAudio
}
private fun getExpectedMuteAudio(): NCSignalingMessage {
val expectedMuteAudio = NCSignalingMessage()
expectedMuteAudio.roomType = "video"
expectedMuteAudio.type = "mute"
val payload = NCMessagePayload()
payload.name = "audio"
expectedMuteAudio.payload = payload
return expectedMuteAudio
}
private fun getExpectedUnmuteVideo(): NCSignalingMessage {
val expectedUnmuteVideo = NCSignalingMessage()
expectedUnmuteVideo.roomType = "video"
expectedUnmuteVideo.type = "unmute"
val payload = NCMessagePayload()
payload.name = "video"
expectedUnmuteVideo.payload = payload
return expectedUnmuteVideo
}
private fun getExpectedMuteVideo(): NCSignalingMessage {
val expectedMuteVideo = NCSignalingMessage()
expectedMuteVideo.roomType = "video"
expectedMuteVideo.type = "mute"
val payload = NCMessagePayload()
payload.name = "video"
expectedMuteVideo.payload = payload
return expectedMuteVideo
}
@Test
fun testStateSentWithExponentialBackoffWhenParticipantAdded() {
val testScheduler = TestScheduler()
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
localCallParticipantModel,
mockedMessageSender
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
// Sending will be done in another thread, so just adding the participant does not send anything until that
// other thread could run.
Mockito.verifyNoInteractions(mockedMessageSender)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
var messageCount = 1
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
messageCount = 2
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
messageCount = 3
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
messageCount = 4
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(8, TimeUnit.SECONDS)
messageCount = 5
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(16, TimeUnit.SECONDS)
messageCount = 6
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(100, TimeUnit.SECONDS)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testStateSentWithExponentialBackoffIsTheCurrentState() {
// This test could have been included in "testStateSentWithExponentialBackoffWhenParticipantAdded", but was
// kept separate for clarity.
val testScheduler = TestScheduler()
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
localCallParticipantModel,
mockedMessageSender
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
// Sending will be done in another thread, so just adding the participant does not send anything until that
// other thread could run.
Mockito.verifyNoInteractions(mockedMessageSender)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
localCallParticipantModel!!.isSpeaking = false
val expectedStoppedSpeaking = DataChannelMessage("stoppedSpeaking")
// Changing the state causes the normal state update to be sent, independently of the initial state
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedStoppedSpeaking)
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedStoppedSpeaking)
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(2)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(2)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
localCallParticipantModel!!.isAudioEnabled = false
val expectedAudioOff = DataChannelMessage("audioOff")
val expectedMuteAudio = getExpectedMuteAudio()
// Changing the state causes the normal state update to be sent, independently of the initial state
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedAudioOff)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteAudio)
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedAudioOff)
Mockito.verify(mockedMessageSender!!, times(3)).sendToAll(expectedStoppedSpeaking)
Mockito.verify(mockedMessageSender!!, times(3)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteAudio)
Mockito.verify(mockedMessageSender!!, times(1)).send(expectedMuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(3)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
localCallParticipantModel!!.isVideoEnabled = false
val expectedVideoOff = DataChannelMessage("videoOff")
val expectedMuteVideo = getExpectedMuteVideo()
// Changing the state causes the normal state update to be sent, independently of the initial state
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedVideoOff)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteVideo)
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
Mockito.verify(mockedMessageSender!!, times(3)).sendToAll(expectedAudioOff)
Mockito.verify(mockedMessageSender!!, times(4)).sendToAll(expectedStoppedSpeaking)
Mockito.verify(mockedMessageSender!!, times(2)).sendToAll(expectedVideoOff)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteAudio)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteVideo)
Mockito.verify(mockedMessageSender!!, times(2)).send(expectedMuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(1)).send(expectedMuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
localCallParticipantModel!!.isVideoEnabled = true
// Changing the state causes the normal state update to be sent, independently of the initial state
Mockito.verify(mockedMessageSender!!, times(4)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedUnmuteVideo)
testScheduler.advanceTimeBy(8, TimeUnit.SECONDS)
Mockito.verify(mockedMessageSender!!, times(4)).sendToAll(expectedAudioOff)
Mockito.verify(mockedMessageSender!!, times(5)).sendToAll(expectedStoppedSpeaking)
Mockito.verify(mockedMessageSender!!, times(5)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteAudio)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedMuteVideo)
Mockito.verify(mockedMessageSender!!, times(1)).sendToAll(expectedUnmuteVideo)
Mockito.verify(mockedMessageSender!!, times(3)).send(expectedMuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(4)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testStateSentWithExponentialBackoffWhenAnotherParticipantAdded() {
// The state sent through data channels should be restarted, although the state sent through signaling
// messages should be independent for each participant.
val testScheduler = TestScheduler()
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
localCallParticipantModel,
mockedMessageSender
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
// Sending will be done in another thread, so just adding the participant does not send anything until that
// other thread could run.
Mockito.verifyNoInteractions(mockedMessageSender)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
var dataChannelMessageCount = 1
var signalingMessageCount1 = 1
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
dataChannelMessageCount = 2
signalingMessageCount1 = 2
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
dataChannelMessageCount = 3
signalingMessageCount1 = 3
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
dataChannelMessageCount = 4
signalingMessageCount1 = 4
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
val callParticipantModel2 = MutableCallParticipantModel("theSessionId2")
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel2)
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
dataChannelMessageCount = 5
var signalingMessageCount2 = 1
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
dataChannelMessageCount = 6
signalingMessageCount2 = 2
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
dataChannelMessageCount = 7
signalingMessageCount2 = 3
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
dataChannelMessageCount = 8
signalingMessageCount2 = 4
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
// 0+1+2+4+1=8 seconds since last signaling messages for participant 1
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
signalingMessageCount1 = 5
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
// 1+7=8 seconds since last data channel messages and signaling messages for participant 2
testScheduler.advanceTimeBy(7, TimeUnit.SECONDS)
dataChannelMessageCount = 9
signalingMessageCount2 = 5
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
// 7+9=16 seconds since last signaling messages for participant 1
testScheduler.advanceTimeBy(9, TimeUnit.SECONDS)
signalingMessageCount1 = 6
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
// 9+7=16 seconds since last data channel messages and signaling messages for participant 2
testScheduler.advanceTimeBy(7, TimeUnit.SECONDS)
dataChannelMessageCount = 10
signalingMessageCount2 = 6
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount1)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount2)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(100, TimeUnit.SECONDS)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testStateSentWithExponentialBackoffWhenParticipantRemoved() {
// For simplicity the exponential backoff is not aborted when the participant that triggered it is removed.
// However, the signaling messages are stopped when the participant is removed.
val testScheduler = TestScheduler()
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
localCallParticipantModel,
mockedMessageSender
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
// Sending will be done in another thread, so just adding the participant does not send anything until that
// other thread could run.
Mockito.verifyNoInteractions(mockedMessageSender)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
var dataChannelMessageCount = 1
var signalingMessageCount = 1
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
dataChannelMessageCount = 2
signalingMessageCount = 2
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
dataChannelMessageCount = 3
signalingMessageCount = 3
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(4, TimeUnit.SECONDS)
dataChannelMessageCount = 4
signalingMessageCount = 4
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
localStateBroadcasterMcu!!.handleCallParticipantRemoved(callParticipantModel)
testScheduler.advanceTimeBy(8, TimeUnit.SECONDS)
dataChannelMessageCount = 5
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(16, TimeUnit.SECONDS)
dataChannelMessageCount = 6
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(dataChannelMessageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(signalingMessageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(100, TimeUnit.SECONDS)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testStateNoLongerSentOnceDestroyed() {
val testScheduler = TestScheduler()
RxJavaPlugins.setIoSchedulerHandler { testScheduler }
localStateBroadcasterMcu = LocalStateBroadcasterMcu(
localCallParticipantModel,
mockedMessageSender
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
val callParticipantModel2 = MutableCallParticipantModel("theSessionId2")
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel)
localStateBroadcasterMcu!!.handleCallParticipantAdded(callParticipantModel2)
// Sending will be done in another thread, so just adding the participant does not send anything until that
// other thread could run.
Mockito.verifyNoInteractions(mockedMessageSender)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
testScheduler.advanceTimeBy(0, TimeUnit.SECONDS)
var messageCount = 1
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
messageCount = 2
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
testScheduler.advanceTimeBy(2, TimeUnit.SECONDS)
messageCount = 3
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!, times(messageCount)).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSender!!, times(messageCount)).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSender)
localStateBroadcasterMcu!!.destroy()
testScheduler.advanceTimeBy(100, TimeUnit.SECONDS)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
}

View file

@ -0,0 +1,357 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.models.json.signaling.NCMessagePayload
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.webrtc.PeerConnection
class LocalStateBroadcasterNoMcuTest {
private var localCallParticipantModel: MutableLocalCallParticipantModel? = null
private var mockedMessageSenderNoMcu: MessageSenderNoMcu? = null
private var localStateBroadcasterNoMcu: LocalStateBroadcasterNoMcu? = null
@Before
fun setUp() {
localCallParticipantModel = MutableLocalCallParticipantModel()
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.isVideoEnabled = true
mockedMessageSenderNoMcu = Mockito.mock(MessageSenderNoMcu::class.java)
}
private fun getExpectedUnmuteAudio(): NCSignalingMessage {
val expectedUnmuteAudio = NCSignalingMessage()
expectedUnmuteAudio.roomType = "video"
expectedUnmuteAudio.type = "unmute"
val payload = NCMessagePayload()
payload.name = "audio"
expectedUnmuteAudio.payload = payload
return expectedUnmuteAudio
}
private fun getExpectedUnmuteVideo(): NCSignalingMessage {
val expectedUnmuteVideo = NCSignalingMessage()
expectedUnmuteVideo.roomType = "video"
expectedUnmuteVideo.type = "unmute"
val payload = NCMessagePayload()
payload.name = "video"
expectedUnmuteVideo.payload = payload
return expectedUnmuteVideo
}
@Test
fun testStateSentWhenIceConnected() {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateSentWhenIceCompleted() {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateNotSentWhenIceCompletedAfterConnected() {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateNotSentWhenIceConnectedAgain() {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
// Completed -> Connected could happen with an ICE restart
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.DISCONNECTED)
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
// Failed -> Checking could happen with an ICE restart
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.FAILED)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateNotSentToOtherParticipantsWhenIceConnected() {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
val callParticipantModel2 = MutableCallParticipantModel("theSessionId2")
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel2)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
callParticipantModel2.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
callParticipantModel2.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedAudioOn, "theSessionId2")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedSpeaking, "theSessionId2")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedVideoOn, "theSessionId2")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteAudio, "theSessionId2")
Mockito.verify(mockedMessageSenderNoMcu!!).send(expectedUnmuteVideo, "theSessionId2")
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateNotSentWhenIceConnectedAfterParticipantIsRemoved() {
// This should not happen, as peer connections are expected to be ended when a call participant is removed, but
// just in case.
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
localStateBroadcasterNoMcu!!.handleCallParticipantRemoved(callParticipantModel)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateNotSentWhenIceCompletedAfterParticipantIsRemoved() {
// This should not happen, as peer connections are expected to be ended when a call participant is removed, but
// just in case.
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
localStateBroadcasterNoMcu!!.handleCallParticipantRemoved(callParticipantModel)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateNotSentWhenIceConnectedAfterDestroyed() {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
val callParticipantModel2 = MutableCallParticipantModel("theSessionId2")
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel2)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
callParticipantModel2.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
localStateBroadcasterNoMcu!!.destroy()
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
callParticipantModel2.setIceConnectionState(PeerConnection.IceConnectionState.CONNECTED)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateNotSentWhenIceCompletedAfterDestroyed() {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu
)
val callParticipantModel = MutableCallParticipantModel("theSessionId")
localStateBroadcasterNoMcu!!.handleCallParticipantAdded(callParticipantModel)
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.CHECKING)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
localStateBroadcasterNoMcu!!.destroy()
callParticipantModel.setIceConnectionState(PeerConnection.IceConnectionState.COMPLETED)
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
}
}

View file

@ -0,0 +1,324 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.models.json.signaling.NCMessagePayload
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
@Suppress("TooManyFunctions")
class LocalStateBroadcasterTest {
private class LocalStateBroadcaster(
localCallParticipantModel: LocalCallParticipantModel?,
messageSender: MessageSender?
) : com.nextcloud.talk.call.LocalStateBroadcaster(localCallParticipantModel, messageSender) {
override fun handleCallParticipantAdded(callParticipantModel: CallParticipantModel) {
// Not used in base class tests
}
override fun handleCallParticipantRemoved(callParticipantModel: CallParticipantModel) {
// Not used in base class tests
}
}
private var localCallParticipantModel: MutableLocalCallParticipantModel? = null
private var mockedMessageSender: MessageSender? = null
private var localStateBroadcaster: LocalStateBroadcaster? = null
@Before
fun setUp() {
localCallParticipantModel = MutableLocalCallParticipantModel()
mockedMessageSender = Mockito.mock(MessageSender::class.java)
}
@Test
fun testEnableAudio() {
localCallParticipantModel!!.isAudioEnabled = false
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isAudioEnabled = true
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedUnmuteAudio = NCSignalingMessage()
expectedUnmuteAudio.roomType = "video"
expectedUnmuteAudio.type = "unmute"
val payload = NCMessagePayload()
payload.name = "audio"
expectedUnmuteAudio.payload = payload
Mockito.verify(mockedMessageSender!!).sendToAll(expectedAudioOn)
Mockito.verify(mockedMessageSender!!).sendToAll(expectedUnmuteAudio)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testEnableAudioTwice() {
localCallParticipantModel!!.isAudioEnabled = true
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isAudioEnabled = true
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testDisableAudio() {
localCallParticipantModel!!.isAudioEnabled = true
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isAudioEnabled = false
val expectedAudioOff = DataChannelMessage("audioOff")
val expectedMuteAudio = NCSignalingMessage()
expectedMuteAudio.roomType = "video"
expectedMuteAudio.type = "mute"
val payload = NCMessagePayload()
payload.name = "audio"
expectedMuteAudio.payload = payload
Mockito.verify(mockedMessageSender!!).sendToAll(expectedAudioOff)
Mockito.verify(mockedMessageSender!!).sendToAll(expectedMuteAudio)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testDisableAudioTwice() {
localCallParticipantModel!!.isAudioEnabled = false
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isAudioEnabled = false
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testEnableSpeaking() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = false
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isSpeaking = true
val expectedSpeaking = DataChannelMessage("speaking")
Mockito.verify(mockedMessageSender!!).sendToAll(expectedSpeaking)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testEnableSpeakingTwice() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = true
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isSpeaking = true
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testEnableSpeakingWithAudioDisabled() {
localCallParticipantModel!!.isAudioEnabled = false
localCallParticipantModel!!.isSpeaking = false
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isSpeaking = true
Mockito.verifyNoInteractions(mockedMessageSender)
}
@Test
fun testEnableAudioWhileSpeaking() {
localCallParticipantModel!!.isAudioEnabled = false
localCallParticipantModel!!.isSpeaking = false
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.isAudioEnabled = true
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedUnmuteAudio = NCSignalingMessage()
expectedUnmuteAudio.roomType = "video"
expectedUnmuteAudio.type = "unmute"
val payload = NCMessagePayload()
payload.name = "audio"
expectedUnmuteAudio.payload = payload
val inOrder = Mockito.inOrder(mockedMessageSender)
inOrder.verify(mockedMessageSender!!).sendToAll(expectedAudioOn)
inOrder.verify(mockedMessageSender!!).sendToAll(expectedSpeaking)
Mockito.verify(mockedMessageSender!!).sendToAll(expectedUnmuteAudio)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testDisableSpeaking() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = true
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isSpeaking = false
val expectedStoppedSpeaking = DataChannelMessage("stoppedSpeaking")
Mockito.verify(mockedMessageSender!!).sendToAll(expectedStoppedSpeaking)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testDisableSpeakingTwice() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = false
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isSpeaking = false
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testDisableAudioWhileSpeaking() {
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = true
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isAudioEnabled = false
val expectedStoppedSpeaking = DataChannelMessage("stoppedSpeaking")
val expectedAudioOff = DataChannelMessage("audioOff")
val expectedMuteAudio = NCSignalingMessage()
expectedMuteAudio.roomType = "video"
expectedMuteAudio.type = "mute"
val payload = NCMessagePayload()
payload.name = "audio"
expectedMuteAudio.payload = payload
val inOrder = Mockito.inOrder(mockedMessageSender)
inOrder.verify(mockedMessageSender!!).sendToAll(expectedStoppedSpeaking)
inOrder.verify(mockedMessageSender!!).sendToAll(expectedAudioOff)
Mockito.verify(mockedMessageSender!!).sendToAll(expectedMuteAudio)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testDisableSpeakingWithAudioDisabled() {
localCallParticipantModel!!.isAudioEnabled = false
localCallParticipantModel!!.isSpeaking = true
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isSpeaking = false
Mockito.verifyNoInteractions(mockedMessageSender)
}
@Test
fun testEnableVideo() {
localCallParticipantModel!!.isVideoEnabled = false
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isVideoEnabled = true
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteVideo = NCSignalingMessage()
expectedUnmuteVideo.roomType = "video"
expectedUnmuteVideo.type = "unmute"
val payload = NCMessagePayload()
payload.name = "video"
expectedUnmuteVideo.payload = payload
Mockito.verify(mockedMessageSender!!).sendToAll(expectedVideoOn)
Mockito.verify(mockedMessageSender!!).sendToAll(expectedUnmuteVideo)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testEnableVideoTwice() {
localCallParticipantModel!!.isVideoEnabled = true
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isVideoEnabled = true
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testDisableVideo() {
localCallParticipantModel!!.isVideoEnabled = true
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isVideoEnabled = false
val expectedVideoOff = DataChannelMessage("videoOff")
val expectedMuteVideo = NCSignalingMessage()
expectedMuteVideo.roomType = "video"
expectedMuteVideo.type = "mute"
val payload = NCMessagePayload()
payload.name = "video"
expectedMuteVideo.payload = payload
Mockito.verify(mockedMessageSender!!).sendToAll(expectedVideoOff)
Mockito.verify(mockedMessageSender!!).sendToAll(expectedMuteVideo)
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testDisableVideoTwice() {
localCallParticipantModel!!.isVideoEnabled = false
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localCallParticipantModel!!.isVideoEnabled = false
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
@Test
fun testChangeStateAfterDestroying() {
localCallParticipantModel!!.isAudioEnabled = false
localCallParticipantModel!!.isSpeaking = false
localCallParticipantModel!!.isVideoEnabled = false
localStateBroadcaster = LocalStateBroadcaster(localCallParticipantModel, mockedMessageSender)
localStateBroadcaster!!.destroy()
localCallParticipantModel!!.isAudioEnabled = true
localCallParticipantModel!!.isSpeaking = true
localCallParticipantModel!!.isVideoEnabled = true
Mockito.verifyNoMoreInteractions(mockedMessageSender)
}
}

View file

@ -0,0 +1,118 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.webrtc.PeerConnectionWrapper
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.never
class MessageSenderMcuTest {
private var peerConnectionWrappers: MutableList<PeerConnectionWrapper?>? = null
private var peerConnectionWrapper1: PeerConnectionWrapper? = null
private var peerConnectionWrapper2: PeerConnectionWrapper? = null
private var peerConnectionWrapper2Screen: PeerConnectionWrapper? = null
private var peerConnectionWrapper4Screen: PeerConnectionWrapper? = null
private var ownPeerConnectionWrapper: PeerConnectionWrapper? = null
private var ownPeerConnectionWrapperScreen: PeerConnectionWrapper? = null
private var messageSender: MessageSenderMcu? = null
@Before
fun setUp() {
val signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
val callParticipants = HashMap<String, CallParticipant>()
peerConnectionWrappers = ArrayList()
peerConnectionWrapper1 = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(peerConnectionWrapper1!!.sessionId).thenReturn("theSessionId1")
Mockito.`when`(peerConnectionWrapper1!!.videoStreamType).thenReturn("video")
peerConnectionWrappers!!.add(peerConnectionWrapper1)
peerConnectionWrapper2 = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(peerConnectionWrapper2!!.sessionId).thenReturn("theSessionId2")
Mockito.`when`(peerConnectionWrapper2!!.videoStreamType).thenReturn("video")
peerConnectionWrappers!!.add(peerConnectionWrapper2)
peerConnectionWrapper2Screen = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(peerConnectionWrapper2Screen!!.sessionId).thenReturn("theSessionId2")
Mockito.`when`(peerConnectionWrapper2Screen!!.videoStreamType).thenReturn("screen")
peerConnectionWrappers!!.add(peerConnectionWrapper2Screen)
peerConnectionWrapper4Screen = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(peerConnectionWrapper4Screen!!.sessionId).thenReturn("theSessionId4")
Mockito.`when`(peerConnectionWrapper4Screen!!.videoStreamType).thenReturn("screen")
peerConnectionWrappers!!.add(peerConnectionWrapper4Screen)
ownPeerConnectionWrapper = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(ownPeerConnectionWrapper!!.sessionId).thenReturn("ownSessionId")
Mockito.`when`(ownPeerConnectionWrapper!!.videoStreamType).thenReturn("video")
peerConnectionWrappers!!.add(ownPeerConnectionWrapper)
ownPeerConnectionWrapperScreen = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(ownPeerConnectionWrapperScreen!!.sessionId).thenReturn("ownSessionId")
Mockito.`when`(ownPeerConnectionWrapperScreen!!.videoStreamType).thenReturn("screen")
peerConnectionWrappers!!.add(ownPeerConnectionWrapperScreen)
messageSender = MessageSenderMcu(
signalingMessageSender,
callParticipants.keys,
peerConnectionWrappers,
"ownSessionId"
)
}
@Test
fun testSendDataChannelMessageToAll() {
val message = DataChannelMessage()
messageSender!!.sendToAll(message)
Mockito.verify(ownPeerConnectionWrapper!!).send(message)
Mockito.verify(ownPeerConnectionWrapperScreen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper1!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2Screen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper4Screen!!, never()).send(message)
}
@Test
fun testSendDataChannelMessageToAllIfOwnScreenPeerConnection() {
peerConnectionWrappers!!.remove(ownPeerConnectionWrapper)
val message = DataChannelMessage()
messageSender!!.sendToAll(message)
Mockito.verify(ownPeerConnectionWrapper!!, never()).send(message)
Mockito.verify(ownPeerConnectionWrapperScreen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper1!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2Screen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper4Screen!!, never()).send(message)
}
@Test
fun testSendDataChannelMessageToAllWithoutOwnPeerConnection() {
peerConnectionWrappers!!.remove(ownPeerConnectionWrapper)
peerConnectionWrappers!!.remove(ownPeerConnectionWrapperScreen)
val message = DataChannelMessage()
messageSender!!.sendToAll(message)
Mockito.verify(ownPeerConnectionWrapper!!, never()).send(message)
Mockito.verify(ownPeerConnectionWrapperScreen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper1!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2Screen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper4Screen!!, never()).send(message)
}
}

View file

@ -0,0 +1,101 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.webrtc.PeerConnectionWrapper
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.never
class MessageSenderNoMcuTest {
private var peerConnectionWrappers: MutableList<PeerConnectionWrapper?>? = null
private var peerConnectionWrapper1: PeerConnectionWrapper? = null
private var peerConnectionWrapper2: PeerConnectionWrapper? = null
private var peerConnectionWrapper2Screen: PeerConnectionWrapper? = null
private var peerConnectionWrapper4Screen: PeerConnectionWrapper? = null
private var messageSender: MessageSenderNoMcu? = null
@Before
fun setUp() {
val signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
val callParticipants = HashMap<String, CallParticipant>()
peerConnectionWrappers = ArrayList()
peerConnectionWrapper1 = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(peerConnectionWrapper1!!.sessionId).thenReturn("theSessionId1")
Mockito.`when`(peerConnectionWrapper1!!.videoStreamType).thenReturn("video")
peerConnectionWrappers!!.add(peerConnectionWrapper1)
peerConnectionWrapper2 = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(peerConnectionWrapper2!!.sessionId).thenReturn("theSessionId2")
Mockito.`when`(peerConnectionWrapper2!!.videoStreamType).thenReturn("video")
peerConnectionWrappers!!.add(peerConnectionWrapper2)
peerConnectionWrapper2Screen = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(peerConnectionWrapper2Screen!!.sessionId).thenReturn("theSessionId2")
Mockito.`when`(peerConnectionWrapper2Screen!!.videoStreamType).thenReturn("screen")
peerConnectionWrappers!!.add(peerConnectionWrapper2Screen)
peerConnectionWrapper4Screen = Mockito.mock(PeerConnectionWrapper::class.java)
Mockito.`when`(peerConnectionWrapper4Screen!!.sessionId).thenReturn("theSessionId4")
Mockito.`when`(peerConnectionWrapper4Screen!!.videoStreamType).thenReturn("screen")
peerConnectionWrappers!!.add(peerConnectionWrapper4Screen)
messageSender = MessageSenderNoMcu(signalingMessageSender, callParticipants.keys, peerConnectionWrappers)
}
@Test
fun testSendDataChannelMessage() {
val message = DataChannelMessage()
messageSender!!.send(message, "theSessionId2")
Mockito.verify(peerConnectionWrapper2!!).send(message)
Mockito.verify(peerConnectionWrapper1!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2Screen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper4Screen!!, never()).send(message)
}
@Test
fun testSendDataChannelMessageIfScreenPeerConnection() {
val message = DataChannelMessage()
messageSender!!.send(message, "theSessionId4")
Mockito.verify(peerConnectionWrapper1!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2Screen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper4Screen!!, never()).send(message)
}
@Test
fun testSendDataChannelMessageIfNoPeerConnection() {
val message = DataChannelMessage()
messageSender!!.send(message, "theSessionId3")
Mockito.verify(peerConnectionWrapper1!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2!!, never()).send(message)
Mockito.verify(peerConnectionWrapper2Screen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper4Screen!!, never()).send(message)
}
@Test
fun testSendDataChannelMessageToAll() {
val message = DataChannelMessage()
messageSender!!.sendToAll(message)
Mockito.verify(peerConnectionWrapper1!!).send(message)
Mockito.verify(peerConnectionWrapper2!!).send(message)
Mockito.verify(peerConnectionWrapper2Screen!!, never()).send(message)
Mockito.verify(peerConnectionWrapper4Screen!!, never()).send(message)
}
}

View file

@ -0,0 +1,134 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.call
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage
import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.webrtc.PeerConnectionWrapper
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.times
import org.mockito.invocation.InvocationOnMock
class MessageSenderTest {
private class MessageSender(
signalingMessageSender: SignalingMessageSender?,
callParticipantSessionIds: Set<String>?,
peerConnectionWrappers: List<PeerConnectionWrapper>?
) : com.nextcloud.talk.call.MessageSender(
signalingMessageSender,
callParticipantSessionIds,
peerConnectionWrappers
) {
override fun sendToAll(dataChannelMessage: DataChannelMessage?) {
// Not used in base class tests
}
}
private var signalingMessageSender: SignalingMessageSender? = null
private var callParticipants: MutableMap<String, CallParticipant>? = null
private var messageSender: MessageSender? = null
@Before
fun setUp() {
signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
callParticipants = HashMap()
val callParticipant1: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId1"] = callParticipant1
val callParticipant2: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId2"] = callParticipant2
val callParticipant3: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId3"] = callParticipant3
val callParticipant4: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId4"] = callParticipant4
val peerConnectionWrappers = ArrayList<PeerConnectionWrapper>()
messageSender = MessageSender(signalingMessageSender, callParticipants!!.keys, peerConnectionWrappers)
}
@Test
fun testSendSignalingMessage() {
val message: NCSignalingMessage = Mockito.mock(NCSignalingMessage::class.java)
messageSender!!.send(message, "theSessionId2")
Mockito.verify(message).to = "theSessionId2"
Mockito.verify(signalingMessageSender!!).send(message)
}
@Test
fun testSendSignalingMessageIfUnknownSessionId() {
val message: NCSignalingMessage = Mockito.mock(NCSignalingMessage::class.java)
messageSender!!.send(message, "unknownSessionId")
Mockito.verify(message).to = "unknownSessionId"
Mockito.verify(signalingMessageSender!!).send(message)
}
@Test
fun testSendSignalingMessageToAll() {
val sentTo: MutableList<String?> = ArrayList()
doAnswer { invocation: InvocationOnMock ->
val arguments = invocation.arguments
val message = (arguments[0] as NCSignalingMessage)
sentTo.add(message.to)
null
}.`when`(signalingMessageSender!!).send(any())
val message = NCSignalingMessage()
messageSender!!.sendToAll(message)
assertTrue(sentTo.contains("theSessionId1"))
assertTrue(sentTo.contains("theSessionId2"))
assertTrue(sentTo.contains("theSessionId3"))
assertTrue(sentTo.contains("theSessionId4"))
Mockito.verify(signalingMessageSender!!, times(4)).send(message)
Mockito.verifyNoMoreInteractions(signalingMessageSender)
}
@Test
fun testSendSignalingMessageToAllWhenParticipantsWereUpdated() {
val callParticipant5: CallParticipant = Mockito.mock(CallParticipant::class.java)
callParticipants!!["theSessionId5"] = callParticipant5
callParticipants!!.remove("theSessionId2")
callParticipants!!.remove("theSessionId3")
val sentTo: MutableList<String?> = ArrayList()
doAnswer { invocation: InvocationOnMock ->
val arguments = invocation.arguments
val message = (arguments[0] as NCSignalingMessage)
sentTo.add(message.to)
null
}.`when`(signalingMessageSender!!).send(any())
val message = NCSignalingMessage()
messageSender!!.sendToAll(message)
assertTrue(sentTo.contains("theSessionId1"))
assertTrue(sentTo.contains("theSessionId4"))
assertTrue(sentTo.contains("theSessionId5"))
Mockito.verify(signalingMessageSender!!, times(3)).send(message)
Mockito.verifyNoMoreInteractions(signalingMessageSender)
}
}

View file

@ -0,0 +1,126 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.contacts
import com.nextcloud.talk.contacts.apiService.FakeItem
import com.nextcloud.talk.contacts.repository.FakeRepositoryError
import com.nextcloud.talk.contacts.repository.FakeRepositorySuccess
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before
import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class)
class ContactsViewModelTest {
private lateinit var viewModel: ContactsViewModel
private val repository: ContactsRepository = FakeRepositorySuccess()
val dispatcher: TestDispatcher = UnconfinedTestDispatcher()
@Before
fun setup() {
Dispatchers.setMain(dispatcher)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Before
fun setUp() {
viewModel = ContactsViewModel(repository)
}
@Test
fun `fetch contacts`() =
runTest {
viewModel = ContactsViewModel(repository)
viewModel.getContactsFromSearchParams()
assert(viewModel.contactsViewState.value is ContactsUiState.Success)
val successState = viewModel.contactsViewState.value as ContactsUiState.Success
assert(successState.contacts == FakeItem.contacts)
}
@Test
fun `test error contacts state`() =
runTest {
viewModel = ContactsViewModel(FakeRepositoryError())
assert(viewModel.contactsViewState.value is ContactsUiState.Error)
val errorState = viewModel.contactsViewState.value as ContactsUiState.Error
assert(errorState.message == "unable to fetch contacts")
}
@Test
fun `update search query`() {
viewModel.updateSearchQuery("Ma")
assert(viewModel.searchQuery.value == "Ma")
}
@Test
fun `initial search query is empty string`() {
viewModel.updateSearchQuery("")
assert(viewModel.searchQuery.value == "")
}
@Test
fun `initial shareType is User`() {
assert(viewModel.shareTypeList.contains(ShareType.User.shareType))
}
@Test
fun `update shareTypes`() {
viewModel.updateShareTypes(listOf(ShareType.Group.shareType))
assert(viewModel.shareTypeList.contains(ShareType.Group.shareType))
}
@Test
fun `initial room state is none`() =
runTest {
assert(viewModel.roomViewState.value is RoomUiState.None)
}
@Test
fun `test success room state`() =
runTest {
viewModel.createRoom("1", "users", "s@gmail.com", null)
assert(viewModel.roomViewState.value is RoomUiState.Success)
val successState = viewModel.roomViewState.value as RoomUiState.Success
assert(successState.conversation == FakeItem.roomOverall.ocs!!.data)
}
@Test
fun `test failure room state`() =
runTest {
viewModel = ContactsViewModel(FakeRepositoryError())
viewModel.createRoom("1", "users", "s@gmail.com", null)
assert(viewModel.roomViewState.value is RoomUiState.Error)
val errorState = viewModel.roomViewState.value as RoomUiState.Error
assert(errorState.message == "unable to create room")
}
@Test
fun `test image uri`() {
val expectedImageUri = "https://mydomain.com/index.php/avatar/vidya/512"
val imageUri = viewModel.getImageUri("vidya", false)
assert(imageUri == expectedImageUri)
}
@Test
fun `test error image uri`() {
val expectedImageUri = "https://mydoman.com/index.php/avatar/vidya/512"
val imageUri = viewModel.getImageUri("vidya", false)
assert(imageUri != expectedImageUri)
}
}

View file

@ -0,0 +1,58 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.contacts.apiService
import com.nextcloud.talk.models.json.autocomplete.AutocompleteOCS
import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall
import com.nextcloud.talk.models.json.autocomplete.AutocompleteUser
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.RoomOCS
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericMeta
import org.mockito.Mockito.mock
object FakeItem {
val contacts: List<AutocompleteUser> =
listOf(
AutocompleteUser(id = "android", label = "Android", source = "users"),
AutocompleteUser(id = "android1", label = "Android 1", source = "users"),
AutocompleteUser(id = "android2", label = "Android 2", source = "users"),
AutocompleteUser(id = "Benny", label = "Benny J", source = "users"),
AutocompleteUser(id = "Benjamin", label = "Benjamin Schmidt", source = "users"),
AutocompleteUser(id = "Chris", label = "Christoph Schmidt", source = "users"),
AutocompleteUser(id = "Daniel", label = "Daniel H", source = "users"),
AutocompleteUser(id = "Dennis", label = "Dennis Richard", source = "users"),
AutocompleteUser(id = "Emma", label = "Emma Jackson", source = "users"),
AutocompleteUser(id = "Emily", label = "Emily Jackson", source = "users"),
AutocompleteUser(id = "Mario", label = "Mario Schmidt", source = "users"),
AutocompleteUser(id = "Maria", label = "Maria Schmidt", source = "users"),
AutocompleteUser(id = "Samsung", label = "Samsung A52", source = "users"),
AutocompleteUser(id = "Tom", label = "Tom Müller", source = "users"),
AutocompleteUser(id = "Tony", label = "Tony Baker", source = "users")
)
val contactsOverall = AutocompleteOverall(
ocs = AutocompleteOCS(
meta = GenericMeta(
status = "ok",
statusCode = 200,
message = "OK"
),
data = contacts
)
)
val roomOverall: RoomOverall = RoomOverall(
ocs = RoomOCS(
meta = GenericMeta(
status = "ok",
statusCode = 200,
message = "OK"
),
data = mock(Conversation::class.java)
)
)
}

View file

@ -0,0 +1,29 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kota@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.contacts.repository
import com.nextcloud.talk.contacts.ContactsRepository
import com.nextcloud.talk.models.json.autocomplete.AutocompleteOverall
import com.nextcloud.talk.models.json.conversations.RoomOverall
class FakeRepositoryError : ContactsRepository {
@Suppress("Detekt.TooGenericExceptionThrown")
override suspend fun getContacts(searchQuery: String?, shareTypes: List<String>): AutocompleteOverall =
throw Exception("unable to fetch contacts")
@Suppress("Detekt.TooGenericExceptionThrown")
override suspend fun createRoom(
roomType: String,
sourceType: String?,
userId: String,
conversationName: String?
): RoomOverall = throw Exception("unable to create room")
override fun getImageUri(avatarId: String, requestBigSize: Boolean) =
"https://mydoman.com/index.php/avatar/$avatarId/512"
}

View file

@ -0,0 +1,21 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@email.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.contacts.repository
import com.nextcloud.talk.contacts.ContactsRepository
import com.nextcloud.talk.contacts.apiService.FakeItem
class FakeRepositorySuccess : ContactsRepository {
override suspend fun getContacts(searchQuery: String?, shareTypes: List<String>) = FakeItem.contactsOverall
override suspend fun createRoom(roomType: String, sourceType: String?, userId: String, conversationName: String?) =
FakeItem.roomOverall
override fun getImageUri(avatarId: String, requestBigSize: Boolean) =
"https://mydomain.com/index.php/avatar/$avatarId/512"
}

View file

@ -0,0 +1,25 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.conversationinfo.viewmodel
import org.junit.Test
import org.junit.Assert.assertEquals
class ConversationInfoViewModelTest {
@Test
fun `createConversationNameByParticipants should combine names correctly`() {
val original = listOf("Dave", null, "Charlie")
val all = listOf("Bob", "Charlie", "Dave", "Alice", null, "Simon")
val expectedName = "Charlie, Dave, Alice, Bob, Simon"
val result = ConversationInfoViewModel.createConversationNameByParticipants(original, all)
assertEquals(expectedName, result)
}
}

View file

@ -0,0 +1,174 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.json
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.data.database.mappers.asEntity
import com.nextcloud.talk.data.database.mappers.asModel
import com.nextcloud.talk.data.database.model.ConversationEntity
import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.participants.Participant
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.io.File
@RunWith(Parameterized::class)
class ConversationConversionTest(private val jsonFileName: String) {
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{index}: testDeserialization({0})")
fun data(): List<String> =
listOf(
"RoomOverallExample_APIv1.json",
"RoomOverallExample_APIv2.json",
"RoomOverallExample_APIv4.json"
)
}
@Test
fun testDeserialization() {
val jsonFile = File("src/test/resources/$jsonFileName")
val jsonString = jsonFile.readText()
val roomOverall: RoomOverall = LoganSquare.parse(jsonString, RoomOverall::class.java)
assertNotNull(roomOverall)
val conversationJson = roomOverall.ocs!!.data!!
assertNotNull(conversationJson)
val conversationEntity = conversationJson.asEntity(1)
assertNotNull(conversationEntity)
val apiVersion: Int = jsonFileName.substringAfterLast("APIv").first().digitToInt()
checkConversationEntity(conversationEntity, apiVersion)
val conversationModel = conversationEntity.asModel()
val conversationEntityConvertedBack = conversationModel.asEntity()
checkConversationEntity(conversationEntityConvertedBack, apiVersion)
}
private fun checkConversationEntity(conversationEntity: ConversationEntity, apiVersion: Int) {
assertEquals("1@juwd77g6", conversationEntity.internalId)
assertEquals(1, conversationEntity.accountId)
// check if default values are set for the fields when API_V1 is used
if (apiVersion == 1) {
checkConversationEntityV1(conversationEntity)
}
if (apiVersion >= 1) {
checkConversationEntityLargerThanV1(conversationEntity)
}
if (apiVersion >= 2) {
assertEquals(false, conversationEntity.canDeleteConversation)
assertEquals(true, conversationEntity.canLeaveConversation)
}
if (apiVersion >= 3) {
assertEquals("test", conversationEntity.description)
// assertEquals("", conversationEntity.attendeeId) // Not implemented
// assertEquals("", conversationEntity.attendeePin) // Not implemented
assertEquals("users", conversationEntity.actorType)
assertEquals("marcel2", conversationEntity.actorId)
// assertEquals("", conversationEntity.listable) // Not implemented
assertEquals(0, conversationEntity.callFlag)
// assertEquals("", conversationEntity.sipEnabled) // Not implemented
// assertEquals("", conversationEntity.canEnableSIP) // Not implemented
assertEquals(92320, conversationEntity.lastCommonReadMessage)
}
if (apiVersion >= 4) {
checkConversationEntityV4(conversationEntity)
}
}
private fun checkConversationEntityLargerThanV1(conversationEntity: ConversationEntity) {
assertEquals("juwd77g6", conversationEntity.token)
assertEquals(ConversationEnums.ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL, conversationEntity.type)
assertEquals("marcel", conversationEntity.name)
assertEquals("Marcel", conversationEntity.displayName)
assertEquals(Participant.ParticipantType.OWNER, conversationEntity.participantType)
assertEquals(
ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_WRITE,
conversationEntity.conversationReadOnlyState
)
assertEquals(1727185155, conversationEntity.lastPing)
assertEquals("0", conversationEntity.sessionId)
assertEquals(false, conversationEntity.hasPassword)
assertEquals(false, conversationEntity.hasCall)
assertEquals(true, conversationEntity.canStartCall)
assertEquals(1727098966, conversationEntity.lastActivity)
assertEquals(false, conversationEntity.favorite)
assertEquals(ConversationEnums.NotificationLevel.ALWAYS, conversationEntity.notificationLevel)
assertEquals(ConversationEnums.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS, conversationEntity.lobbyState)
assertEquals(0, conversationEntity.lobbyTimer)
assertEquals(0, conversationEntity.unreadMessages)
assertEquals(false, conversationEntity.unreadMention)
assertEquals(92320, conversationEntity.lastReadMessage)
assertNotNull(conversationEntity.lastMessage)
assertTrue(conversationEntity.lastMessage is String)
assertTrue(conversationEntity.lastMessage!!.contains("token"))
assertEquals(ConversationEnums.ObjectType.DEFAULT, conversationEntity.objectType)
}
private fun checkConversationEntityV4(conversationEntity: ConversationEntity) {
assertEquals("143a9df3", conversationEntity.avatarVersion)
assertEquals(0, conversationEntity.callStartTime)
assertEquals(0, conversationEntity.callRecording)
assertEquals(false, conversationEntity.unreadMentionDirect)
// assertEquals(, conversationEntity.breakoutRoomMode) // Not implemented
// assertEquals(, conversationEntity.breakoutRoomStatus) // Not implemented
assertEquals("away", conversationEntity.status)
assertEquals("👻", conversationEntity.statusIcon)
assertEquals("buuuuh", conversationEntity.statusMessage)
assertEquals(null, conversationEntity.statusClearAt)
assertEquals("143a9df3", conversationEntity.avatarVersion)
// assertEquals("", conversationEntity.isCustomAvatar) // Not implemented
assertEquals(0, conversationEntity.callStartTime)
assertEquals(0, conversationEntity.callRecording)
// assertEquals("", conversationEntity.recordingConsent) // Not implemented
// assertEquals("", conversationEntity.mentionPermissions) // Not implemented
// assertEquals("", conversationEntity.isArchived) // Not implemented
}
private fun checkConversationEntityV1(conversationEntity: ConversationEntity) {
// default values for API_V2 fields
assertEquals(false, conversationEntity.canDeleteConversation)
assertEquals(true, conversationEntity.canLeaveConversation)
// default values for API_V3 fields
assertEquals("", conversationEntity.description)
assertEquals("", conversationEntity.actorType)
assertEquals("", conversationEntity.actorId)
assertEquals(0, conversationEntity.callFlag)
assertEquals(0, conversationEntity.lastCommonReadMessage)
// default values for API_V4 fields
assertEquals("", conversationEntity.avatarVersion)
assertEquals(0, conversationEntity.callStartTime)
assertEquals(0, conversationEntity.callRecording)
assertEquals(false, conversationEntity.unreadMentionDirect)
assertEquals("", conversationEntity.status)
assertEquals("", conversationEntity.statusIcon)
assertEquals("", conversationEntity.statusMessage)
assertEquals(null, conversationEntity.statusClearAt)
assertEquals("", conversationEntity.avatarVersion)
assertEquals(0, conversationEntity.callStartTime)
assertEquals(0, conversationEntity.callRecording)
}
}

View file

@ -0,0 +1,596 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.login.data
import android.os.Bundle
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.LiveData
import androidx.work.WorkInfo
import com.nextcloud.talk.account.data.LoginRepository
import com.nextcloud.talk.account.data.io.LocalLoginDataSource
import com.nextcloud.talk.account.data.model.LoginCompletion
import com.nextcloud.talk.account.data.model.LoginResponse
import com.nextcloud.talk.account.data.network.NetworkLoginDataSource
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertNull
import junit.framework.TestCase.assertTrue
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.any
import org.mockito.kotlin.never
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
@Suppress("TooManyFunctions", "TooGenericExceptionCaught")
@ExperimentalCoroutinesApi
@RunWith(MockitoJUnitRunner.Silent::class)
class LoginRepositoryTest {
@get:Rule
val rule = InstantTaskExecutorRule()
// Repository dependencies
@Mock
lateinit var networkLoginDataSource: NetworkLoginDataSource
@Mock
lateinit var localLoginDataSource: LocalLoginDataSource
// Additional mocks for LocalLoginDataSource dependencies
@Mock
lateinit var liveData: LiveData<WorkInfo?>
lateinit var repo: LoginRepository
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
repo = LoginRepository(networkLoginDataSource, localLoginDataSource)
}
// ========== pollLogin() Tests ==========
@Test
fun `pollLogin returns successful LoginCompletion when network returns HTTP 200`() =
runTest {
// Arrange
val mockResponse = LoginResponse("token123", "https://server.com/poll", "https://server.com/login")
val successfulLoginData = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(networkLoginDataSource.performLoginFlowV2(mockResponse))
.thenReturn(successfulLoginData)
// Act
val result = repo.pollLogin(mockResponse)
// Assert
assertNotNull(result)
assertEquals(200, result?.status)
assertEquals("https://server.com", result?.server)
assertEquals("testuser", result?.loginName)
assertEquals("apppass123", result?.appPassword)
}
@Test
fun `pollLogin returns null when network returns null`() =
runTest {
// Arrange
val mockResponse = LoginResponse("token123", "https://server.com/poll", "https://server.com/login")
whenever(networkLoginDataSource.performLoginFlowV2(mockResponse))
.thenReturn(null)
// Act
val result = repo.pollLogin(mockResponse)
// Assert
assertNull(result)
}
@Test
fun `pollLogin continues polling when status is not HTTP 200 then returns successful result`() =
runTest {
// Arrange
val mockResponse = LoginResponse("token123", "https://server.com/poll", "https://server.com/login")
val pendingLoginData = LoginCompletion(202, "https://server.com", "testuser", "apppass123")
val successfulLoginData = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(networkLoginDataSource.performLoginFlowV2(mockResponse))
.thenReturn(pendingLoginData)
.thenReturn(successfulLoginData)
// Act
val result = repo.pollLogin(mockResponse)
// Assert
assertNotNull(result)
assertEquals(200, result?.status)
verify(networkLoginDataSource, times(2)).performLoginFlowV2(mockResponse)
}
@Test
fun `pollLogin handles slow connection by continuing to poll with delays`() =
runTest {
// Arrange
val mockResponse = LoginResponse("token123", "https://server.com/poll", "https://server.com/login")
val slowResponse1 = LoginCompletion(202, "https://server.com", "testuser", "apppass123")
val slowResponse2 = LoginCompletion(404, "https://server.com", "testuser", "apppass123")
val slowResponse3 = LoginCompletion(500, "https://server.com", "testuser", "apppass123")
val successResponse = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(networkLoginDataSource.performLoginFlowV2(mockResponse))
.thenReturn(slowResponse1)
.thenReturn(slowResponse2)
.thenReturn(slowResponse3)
.thenReturn(successResponse)
// Act
val result = repo.pollLogin(mockResponse)
// Assert
assertNotNull(result)
assertEquals(200, result?.status)
verify(networkLoginDataSource, times(4)).performLoginFlowV2(mockResponse)
}
@Test
fun `pollLogin handles network timeouts during slow connection gracefully`() =
runTest {
// Arrange
val mockResponse = LoginResponse("token123", "https://server.com/poll", "https://server.com/login")
val timeoutResponse = LoginCompletion(408, "https://server.com", "testuser", "apppass123")
val successResponse = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(networkLoginDataSource.performLoginFlowV2(mockResponse))
.thenReturn(timeoutResponse)
.thenReturn(timeoutResponse)
.thenReturn(successResponse)
// Act
val result = repo.pollLogin(mockResponse)
// Assert
assertNotNull(result)
assertEquals(200, result?.status)
verify(networkLoginDataSource, times(3)).performLoginFlowV2(mockResponse)
}
@Test
fun `pollLogin stops when cancelLoginFlow is called`() =
runTest {
// Arrange
val mockResponse = LoginResponse("token123", "https://server.com/poll", "https://server.com/login")
val pendingLoginData = LoginCompletion(
202,
"https://server.com",
"testuser",
"apppass123"
)
whenever(networkLoginDataSource.performLoginFlowV2(mockResponse))
.thenReturn(pendingLoginData)
// Act - cancel before polling
repo.cancelLoginFlow()
val result = repo.pollLogin(mockResponse)
// Assert
assertNull(result)
verify(networkLoginDataSource, never()).performLoginFlowV2(any())
}
// ========== startLoginFlowFromQR() Tests ==========
@Test
fun `startLoginFlowFromQR returns LoginCompletion for valid QR data with all parameters`() {
// Arrange
val qrData = "nc://login/user:testuser&server:https%3A//example.com&password:testpass"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNotNull(result)
assertEquals(200, result?.status)
assertEquals("https://example.com", result?.server)
assertEquals("testuser", result?.loginName)
assertEquals("testpass", result?.appPassword)
}
@Test
fun `startLoginFlowFromQR returns LoginCompletion for minimal valid QR data`() {
// Arrange
val qrData = "nc://login/server:https%3A//example.com"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNull(result)
}
@Test
fun `startLoginFlowFromQR returns null for invalid prefix`() {
// Arrange
val qrData = "invalid://login/user:testuser&server:https://example.com"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNull(result)
}
@Test
fun `startLoginFlowFromQR returns null when too many arguments provided`() {
// Arrange
val qrData = "nc://login/user:test&server:https://example.com&password:pass&extra:value"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNull(result)
}
@Test
fun `startLoginFlowFromQR returns null for empty data`() {
// Arrange
val qrData = "nc://login/"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNull(result)
}
@Test
fun `startLoginFlowFromQR sets reAuth flag correctly`() {
// Arrange
val qrData = "nc://login/server:https%3A//example.com"
// Act
val result = repo.startLoginFlowFromQR(qrData, reAuth = true)
// Assert
assertNull(result)
}
@Test
fun `startLoginFlowFromQR handles URL encoding correctly`() {
// Arrange
val qrData = "nc://login/user:test%40user.com&server:https%3A//example.com%3A8080&password:test%26pass"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNotNull(result)
assertEquals("test@user.com", result?.loginName)
assertEquals("https://example.com:8080", result?.server)
assertEquals("test&pass", result?.appPassword)
}
@Test
fun `startLoginFlowFromQR handles mixed parameter order`() {
// Arrange
val qrData = "nc://login/password:testpass&user:testuser&server:https%3A//example.com"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNotNull(result)
assertEquals("https://example.com", result?.server)
assertEquals("testuser", result?.loginName)
assertEquals("testpass", result?.appPassword)
}
// ========== startLoginFlow() Tests ==========
@Test
fun `startLoginFlow returns LoginResponse from network`() =
runTest {
// Arrange
val baseUrl = "https://example.com"
val mockResponse = LoginResponse("token123", "https://example.com/poll", "https://example.com/login")
whenever(networkLoginDataSource.anonymouslyPostLoginRequest(baseUrl))
.thenReturn(mockResponse)
// Act
val result = repo.startLoginFlow(baseUrl)
// Assert
assertEquals(mockResponse, result)
verify(networkLoginDataSource).anonymouslyPostLoginRequest(baseUrl)
}
@Test
fun `startLoginFlow returns null when network returns null`() =
runTest {
// Arrange
val baseUrl = "https://example.com"
whenever(networkLoginDataSource.anonymouslyPostLoginRequest(baseUrl))
.thenReturn(null)
// Act
val result = repo.startLoginFlow(baseUrl)
// Assert
assertNull(result)
}
@Test
fun `startLoginFlow sets reAuth flag correctly`() =
runTest {
// Arrange
val baseUrl = "https://example.com"
val mockResponse = LoginResponse("token123", "https://example.com/poll", "https://example.com/login")
whenever(networkLoginDataSource.anonymouslyPostLoginRequest(baseUrl))
.thenReturn(mockResponse)
// Act
val result = repo.startLoginFlow(baseUrl, reAuth = true)
// Assert
assertEquals(mockResponse, result)
}
@Test
fun `startLoginFlow handles network SSL exceptions`() =
runTest {
// Arrange
val baseUrl = "https://example.com"
whenever(networkLoginDataSource.anonymouslyPostLoginRequest(baseUrl))
.thenReturn(null) // NetworkLoginDataSource catches SSL exceptions and returns null
// Act
val result = repo.startLoginFlow(baseUrl)
// Assert
assertNull(result)
verify(networkLoginDataSource).anonymouslyPostLoginRequest(baseUrl)
}
// ========== cancelLoginFlow() Tests ==========
@Test
fun `cancelLoginFlow stops polling loop`() =
runTest {
// Arrange
val mockResponse = LoginResponse("token123", "https://server.com/poll", "https://server.com/login")
val pendingLoginData = LoginCompletion(202, "https://server.com", "testuser", "apppass123")
whenever(networkLoginDataSource.performLoginFlowV2(mockResponse))
.thenReturn(pendingLoginData)
// Act
repo.cancelLoginFlow()
val result = repo.pollLogin(mockResponse)
// Assert
assertNull(result)
}
// ========== parseAndLogin() Tests ==========
@Test
fun `parseAndLogin returns null when user is scheduled for deletion`() {
// Arrange
val loginData = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(localLoginDataSource.checkIfUserIsScheduledForDeletion(loginData))
.thenReturn(true)
whenever(localLoginDataSource.startAccountRemovalWorker())
.thenReturn(liveData)
// Act
val result = repo.parseAndLogin(loginData)
// Assert
assertNull(result)
verify(localLoginDataSource).startAccountRemovalWorker()
}
@Test
fun `parseAndLogin returns null when user exists and reAuth is false`() {
// Arrange
val loginData = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(localLoginDataSource.checkIfUserIsScheduledForDeletion(loginData))
.thenReturn(false)
whenever(localLoginDataSource.checkIfUserExists(loginData))
.thenReturn(true)
// Act
val result = repo.parseAndLogin(loginData)
// Assert
assertNull(result)
verify(localLoginDataSource, never()).updateUser(any())
}
@Test
fun `parseAndLogin updates user when user exists and reAuth is true`() {
// Arrange - First set reAuth to true via QR flow
val qrData = "nc://login/server:https%3A//example.com"
repo.startLoginFlowFromQR(qrData, reAuth = true)
val loginData = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(localLoginDataSource.checkIfUserIsScheduledForDeletion(loginData))
.thenReturn(false)
whenever(localLoginDataSource.checkIfUserExists(loginData))
.thenReturn(true)
// Act
val result = repo.parseAndLogin(loginData)
// Assert
assertNull(result)
verify(localLoginDataSource).updateUser(loginData)
}
@Test
fun `parseAndLogin returns Bundle for new user with https protocol`() {
// Arrange
val loginData = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(localLoginDataSource.checkIfUserIsScheduledForDeletion(loginData))
.thenReturn(false)
whenever(localLoginDataSource.checkIfUserExists(loginData))
.thenReturn(false)
// Act
val result = repo.parseAndLogin(loginData)
// Assert
assertNotNull(result)
assertTrue(result is Bundle)
}
@Test
fun `parseAndLogin returns Bundle for new user with http protocol`() {
// Arrange
val loginData = LoginCompletion(200, "http://server.com", "testuser", "apppass123")
whenever(localLoginDataSource.checkIfUserIsScheduledForDeletion(loginData))
.thenReturn(false)
whenever(localLoginDataSource.checkIfUserExists(loginData))
.thenReturn(false)
// Act
val result = repo.parseAndLogin(loginData)
// Assert
assertNotNull(result)
assertTrue(result is Bundle)
}
@Test
fun `parseAndLogin returns Bundle for new user without protocol prefix`() {
// Arrange
val loginData = LoginCompletion(200, "server.com", "testuser", "apppass123")
whenever(localLoginDataSource.checkIfUserIsScheduledForDeletion(loginData))
.thenReturn(false)
whenever(localLoginDataSource.checkIfUserExists(loginData))
.thenReturn(false)
// Act
val result = repo.parseAndLogin(loginData)
// Assert
assertNotNull(result)
assertTrue(result is Bundle)
}
// ========== LocalLoginDataSource Integration Tests ==========
@Test
fun `parseAndLogin properly integrates with LocalLoginDataSource checkIfUserIsScheduledForDeletion`() {
// Arrange
val loginData = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(localLoginDataSource.checkIfUserIsScheduledForDeletion(loginData))
.thenReturn(true)
whenever(localLoginDataSource.startAccountRemovalWorker())
.thenReturn(liveData)
// Act
repo.parseAndLogin(loginData)
// Assert
verify(localLoginDataSource).checkIfUserIsScheduledForDeletion(loginData)
verify(localLoginDataSource).startAccountRemovalWorker()
}
@Test
fun `parseAndLogin properly integrates with LocalLoginDataSource checkIfUserExists`() {
// Arrange
val loginData = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(localLoginDataSource.checkIfUserIsScheduledForDeletion(loginData))
.thenReturn(false)
whenever(localLoginDataSource.checkIfUserExists(loginData))
.thenReturn(true)
// Act
repo.parseAndLogin(loginData)
// Assert
verify(localLoginDataSource).checkIfUserExists(loginData)
verify(localLoginDataSource, never()).updateUser(any())
}
@Test
fun `parseAndLogin calls updateUser with correct LoginCompletion data`() {
// Arrange - Set reAuth flag first
val qrData = "nc://login/server:https%3A//example.com"
repo.startLoginFlowFromQR(qrData, reAuth = true)
val loginData = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(localLoginDataSource.checkIfUserIsScheduledForDeletion(loginData))
.thenReturn(false)
whenever(localLoginDataSource.checkIfUserExists(loginData))
.thenReturn(true)
// Act
repo.parseAndLogin(loginData)
// Assert
verify(localLoginDataSource).updateUser(loginData)
}
// ========== Edge Cases and Error Handling ==========
@Test
fun `pollLogin handles performLoginFlowV2 returning error status codes`() =
runTest {
// Arrange
val mockResponse = LoginResponse("token123", "https://server.com/poll", "https://server.com/login")
val errorResponse = LoginCompletion(404, "", "", "")
val successResponse = LoginCompletion(200, "https://server.com", "testuser", "apppass123")
whenever(networkLoginDataSource.performLoginFlowV2(mockResponse))
.thenReturn(errorResponse)
.thenReturn(successResponse)
// Act
val result = repo.pollLogin(mockResponse)
// Assert
assertNotNull(result)
assertEquals(200, result?.status)
}
@Test
fun `startLoginFlowFromQR handles malformed URL gracefully`() {
// Arrange
val qrData = "nc://login/malformed&data&without&proper&key:value"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNull(result) // Should still create LoginCompletion with empty values
}
@Test
fun `startLoginFlowFromQR handles partial parameter data`() {
// Arrange
val qrData = "nc://login/user:testuser&server:"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNull(result)
}
}

View file

@ -0,0 +1,167 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.login.data.network
import com.nextcloud.talk.account.data.model.LoginResponse
import com.nextcloud.talk.account.data.network.NetworkLoginDataSource
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertNull
import okhttp3.OkHttpClient
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.Before
import org.junit.Test
import org.mockito.MockitoAnnotations
@Suppress("ktlint:standard:max-line-length", "MaxLineLength")
class NetworkLoginDataSourceTest {
lateinit var network: NetworkLoginDataSource
private val okHttpClient: OkHttpClient = OkHttpClient.Builder().build()
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
network = NetworkLoginDataSource(okHttpClient)
}
@Test
fun `testing anonymouslyPostLoginRequest correct path`() {
val server = MockWebServer()
server.start(0)
val httpUrl = server.url("index.php/login/v2")
val validResponse = """
{
"poll":{
"token":"mQUYQdffOSAMJYtm8pVpkOsVqXt5hglnuSpO5EMbgJMNEPFGaiDe8OUjvrJ2WcYcBSLgqynu9jaPFvZHMl83ybMvp6aDIDARjTFIBpRWod6p32fL9LIpIStvc6k8Wrs1",
"endpoint":"https:\/\/cloud.example.com\/login\/v2\/poll"
},
"login":"https:\/\/cloud.example.com\/login\/v2\/flow\/guyjGtcKPTKCi4epIRIupIexgJ8wNInMFSfHabACRPZUkmEaWZSM54bFkFuzWksbps7jmTFQjeskLpyJXyhpHlgK8sZBn9HXLXjohIx5iXgJKdOkkZTYCzUWHlsg3YFg"
}
""".trimIndent()
val mockResponse = MockResponse().setBody(validResponse)
server.enqueue(mockResponse)
val loginResponse = network.anonymouslyPostLoginRequest(httpUrl.toString())
assertNotNull(loginResponse)
}
@Test
fun `testing anonymouslyPostLoginRequest error path`() {
val server = MockWebServer()
val invalidResponse = MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setResponseCode(404)
.setBody("{}")
server.start()
server.enqueue(invalidResponse)
val httpUrl = server.url("index.php/login/v2")
val loginResponse = network.anonymouslyPostLoginRequest(httpUrl.toString())
assertNull(loginResponse)
}
@Test
fun `testing anonymouslyPostLoginRequest malformed response`() {
val server = MockWebServer()
val validResponse = """
{
"poll":{
"token":"mQUYQdffOSAMJYtm8pVpkOsVqXt5hglnuSpO5EMbgJMNEPFGaiDe8OUjvrJ2WcYcBSLgqynu9jaPFvZHMl83ybMvp6aDIDARjTFIBpRWod6p32fL9LIpIStvc6k8Wrs1"
},
"login":"https:\/\/cloud.example.com\/login\/v2\/flow\/guyjGtcKPTKCi4epIRIupIexgJ8wNInMFSfHabACRPZUkmEaWZSM54bFkFuzWksbps7jmTFQjeskLpyJXyhpHlgK8sZBn9HXLXjohIx5iXgJKdOkkZTYCzUWHlsg3YFg"
}
""".trimIndent()
val mockResponse = MockResponse().setBody(validResponse)
server.enqueue(mockResponse)
server.start()
val httpUrl = server.url("index.php/login/v2")
val loginResponse = network.anonymouslyPostLoginRequest(httpUrl.toString())
assertNull(loginResponse)
}
@Test
fun `testing performLoginFlowV2 correct path`() {
val server = MockWebServer()
val validBody = """
{
"server":"https:\/\/cloud.example.com",
"loginName":"username",
"appPassword":"yKTVA4zgxjfivy52WqD8kW3M2pKGQr6srmUXMipRdunxjPFripJn0GMfmtNOqOolYSuJ6sCN"
}
""".trimIndent()
val validResponse = MockResponse()
.setBody(validBody)
server.enqueue(validResponse)
server.start()
val httpUrl = server.url("login/v2/poll")
val loginResponse = LoginResponse(
token = "mQUYQdffOSAMJYtm8pVpkOsVqXt5hglnuSpO5EMbgJMNEPFGaiDe8OUjvrJ2WcYcBSLgqynu9jaPFvZHMl83ybMvp6aDIDARjTFIBpRWod6p32fL9LIpIStvc6k8Wrs1",
pollUrl = httpUrl.toString(),
loginUrl = "https:\\/\\/cloud.example.com\\/login\\/v2\\/flow\\/guyjGtcKPTKCi4epIRIupIexgJ8wNInMFSfHabACRPZUkmEaWZSM54bFkFuzWksbps7jmTFQjeskLpyJXyhpHlgK8sZBn9HXLXjohIx5iXgJKdOkkZTYCzUWHlsg3YFg"
)
val loginCompletion = network.performLoginFlowV2(loginResponse)
assertNotNull(loginCompletion)
}
@Test
fun `testing performLoginFlowV2 error path`() {
val server = MockWebServer()
val invalidResponse = MockResponse()
.addHeader("Content-Type", "application/json; charset=utf-8")
.addHeader("Cache-Control", "no-cache")
.setResponseCode(404)
.setBody("{}")
server.enqueue(invalidResponse)
server.start()
val httpUrl = server.url("login/v2/poll")
val loginResponse = LoginResponse(
token = "mQUYQdffOSAMJYtm8pVpkOsVqXt5hglnuSpO5EMbgJMNEPFGaiDe8OUjvrJ2WcYcBSLgqynu9jaPFvZHMl83ybMvp6aDIDARjTFIBpRWod6p32fL9LIpIStvc6k8Wrs1",
pollUrl = httpUrl.toString(),
loginUrl = "https:\\/\\/cloud.example.com\\/login\\/v2\\/flow\\/guyjGtcKPTKCi4epIRIupIexgJ8wNInMFSfHabACRPZUkmEaWZSM54bFkFuzWksbps7jmTFQjeskLpyJXyhpHlgK8sZBn9HXLXjohIx5iXgJKdOkkZTYCzUWHlsg3YFg"
)
val loginCompletion = network.performLoginFlowV2(loginResponse)
assert(loginCompletion?.status == 404)
}
@Test
fun `testing performLoginFlowV2 malformed response`() {
val server = MockWebServer()
val validBody = """
{
"server":"https:\/\/cloud.example.com",
"loginName":"username"
}
""".trimIndent()
val validResponse = MockResponse()
.setBody(validBody)
server.enqueue(validResponse)
server.start()
val httpUrl = server.url("login/v2/poll")
val loginResponse = LoginResponse(
token = "mQUYQdffOSAMJYtm8pVpkOsVqXt5hglnuSpO5EMbgJMNEPFGaiDe8OUjvrJ2WcYcBSLgqynu9jaPFvZHMl83ybMvp6aDIDARjTFIBpRWod6p32fL9LIpIStvc6k8Wrs1",
pollUrl = httpUrl.toString(),
loginUrl = "https:\\/\\/cloud.example.com\\/login\\/v2\\/flow\\/guyjGtcKPTKCi4epIRIupIexgJ8wNInMFSfHabACRPZUkmEaWZSM54bFkFuzWksbps7jmTFQjeskLpyJXyhpHlgK8sZBn9HXLXjohIx5iXgJKdOkkZTYCzUWHlsg3YFg"
)
val loginCompletion = network.performLoginFlowV2(loginResponse)
assertNull(loginCompletion)
}
}

View file

@ -0,0 +1,127 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.messagesearch
import com.nextcloud.talk.models.domain.SearchMessageEntry
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import com.nextcloud.talk.test.fakes.FakeUnifiedSearchRepository
import io.reactivex.Observable
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.mockito.MockitoAnnotations
class MessageSearchHelperTest {
val repository = FakeUnifiedSearchRepository()
@Suppress("LongParameterList")
private fun createMessageEntry(
searchTerm: String = "foo",
thumbnailURL: String = "foo",
title: String = "foo",
messageExcerpt: String = "foo",
conversationToken: String = "foo",
messageId: String? = "foo"
) = SearchMessageEntry(searchTerm, thumbnailURL, title, messageExcerpt, conversationToken, messageId)
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
}
@Test
fun emptySearch() {
repository.response = UnifiedSearchRepository.UnifiedSearchResults(0, false, emptyList())
val sut = MessageSearchHelper(repository)
val testObserver = sut.startMessageSearch("foo").test()
testObserver.assertComplete()
testObserver.assertValueCount(1)
val expected = MessageSearchHelper.MessageSearchResults(emptyList(), false)
testObserver.assertValue(expected)
}
@Test
fun nonEmptySearch_withMoreResults() {
val entries = (1..5).map { createMessageEntry() }
repository.response = UnifiedSearchRepository.UnifiedSearchResults(5, true, entries)
val sut = MessageSearchHelper(repository)
val observable = sut.startMessageSearch("foo")
val expected = MessageSearchHelper.MessageSearchResults(entries, true)
testCall(observable, expected)
}
@Test
fun nonEmptySearch_withNoMoreResults() {
val entries = (1..2).map { createMessageEntry() }
repository.response = UnifiedSearchRepository.UnifiedSearchResults(2, false, entries)
val sut = MessageSearchHelper(repository)
val observable = sut.startMessageSearch("foo")
val expected = MessageSearchHelper.MessageSearchResults(entries, false)
testCall(observable, expected)
}
@Test
fun nonEmptySearch_consecutiveSearches_sameResult() {
val entries = (1..2).map { createMessageEntry() }
repository.response = UnifiedSearchRepository.UnifiedSearchResults(2, false, entries)
val sut = MessageSearchHelper(repository)
repeat(5) {
val observable = sut.startMessageSearch("foo")
val expected = MessageSearchHelper.MessageSearchResults(entries, false)
testCall(observable, expected)
}
}
@Test
fun loadMore_noPreviousResults() {
val sut = MessageSearchHelper(repository)
Assert.assertEquals(null, sut.loadMore())
}
@Test
fun loadMore_previousResults_sameSearch() {
val sut = MessageSearchHelper(repository)
val firstPageEntries = (1..5).map { createMessageEntry() }
repository.response = UnifiedSearchRepository.UnifiedSearchResults(5, true, firstPageEntries)
val firstPageObservable = sut.startMessageSearch("foo")
Assert.assertEquals(0, repository.lastRequestedCursor)
val firstPageExpected = MessageSearchHelper.MessageSearchResults(firstPageEntries, true)
testCall(firstPageObservable, firstPageExpected)
val secondPageEntries = (1..5).map { createMessageEntry(title = "bar") }
repository.response = UnifiedSearchRepository.UnifiedSearchResults(10, false, secondPageEntries)
val secondPageObservable = sut.loadMore()
Assert.assertEquals(5, repository.lastRequestedCursor)
Assert.assertNotNull(secondPageObservable)
val secondPageExpected = MessageSearchHelper.MessageSearchResults(firstPageEntries + secondPageEntries, false)
testCall(secondPageObservable!!, secondPageExpected)
}
private fun testCall(
searchCall: Observable<MessageSearchHelper.MessageSearchResults>,
expectedResult: MessageSearchHelper.MessageSearchResults
) {
val testObserver = searchCall.test()
testObserver.assertComplete()
testObserver.assertValueCount(1)
testObserver.assertValue(expectedResult)
testObserver.dispose()
}
}

View file

@ -0,0 +1,262 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.signaling;
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
public class SignalingMessageReceiverCallParticipantTest {
private SignalingMessageReceiver signalingMessageReceiver;
@Before
public void setUp() {
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
// protected methods.
signalingMessageReceiver = new SignalingMessageReceiver() {
};
}
@Test
public void testAddCallParticipantMessageListenerWithNullListener() {
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener(null, "theSessionId");
});
}
@Test
public void testAddCallParticipantMessageListenerWithNullSessionId() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, null);
});
}
@Test
public void testCallParticipantMessageRaiseHand() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("raiseHand");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("raiseHand");
messagePayload.setState(Boolean.TRUE);
messagePayload.setTimestamp(4815162342L);
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedCallParticipantMessageListener, only()).onRaiseHand(true, 4815162342L);
}
@Test
public void testCallParticipantMessageReaction() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("reaction");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("reaction");
messagePayload.setReaction("theReaction");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedCallParticipantMessageListener, only()).onReaction("theReaction");
}
@Test
public void testCallParticipantMessageUnshareScreen() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("unshareScreen");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedCallParticipantMessageListener, only()).onUnshareScreen();
}
@Test
public void testCallParticipantMessageSeveralListenersSameFrom() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener1 =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener2 =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener1, "theSessionId");
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener2, "theSessionId");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("unshareScreen");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedCallParticipantMessageListener1, only()).onUnshareScreen();
verify(mockedCallParticipantMessageListener2, only()).onUnshareScreen();
}
@Test
public void testCallParticipantMessageNotMatchingSessionId() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("notMatchingSessionId");
signalingMessage.setType("unshareScreen");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verifyNoInteractions(mockedCallParticipantMessageListener);
}
@Test
public void testCallParticipantMessageAfterRemovingListener() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
signalingMessageReceiver.removeListener(mockedCallParticipantMessageListener);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("unshareScreen");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verifyNoInteractions(mockedCallParticipantMessageListener);
}
@Test
public void testCallParticipantMessageAfterRemovingSingleListenerOfSeveral() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener1 =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener2 =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener3 =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener1, "theSessionId");
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener2, "theSessionId");
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener3, "theSessionId");
signalingMessageReceiver.removeListener(mockedCallParticipantMessageListener2);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("unshareScreen");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedCallParticipantMessageListener1, only()).onUnshareScreen();
verify(mockedCallParticipantMessageListener3, only()).onUnshareScreen();
verifyNoInteractions(mockedCallParticipantMessageListener2);
}
@Test
public void testCallParticipantMessageAfterAddingListenerAgainForDifferentFrom() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId");
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener, "theSessionId2");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("unshareScreen");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verifyNoInteractions(mockedCallParticipantMessageListener);
signalingMessage.setFrom("theSessionId2");
signalingMessage.setType("unshareScreen");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedCallParticipantMessageListener, only()).onUnshareScreen();
}
@Test
public void testAddCallParticipantMessageListenerWhenHandlingCallParticipantMessage() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener1 =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener2 =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener2, "theSessionId");
return null;
}).when(mockedCallParticipantMessageListener1).onUnshareScreen();
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener1, "theSessionId");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("unshareScreen");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedCallParticipantMessageListener1, only()).onUnshareScreen();
verifyNoInteractions(mockedCallParticipantMessageListener2);
}
@Test
public void testRemoveCallParticipantMessageListenerWhenHandlingCallParticipantMessage() {
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener1 =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
SignalingMessageReceiver.CallParticipantMessageListener mockedCallParticipantMessageListener2 =
mock(SignalingMessageReceiver.CallParticipantMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.removeListener(mockedCallParticipantMessageListener2);
return null;
}).when(mockedCallParticipantMessageListener1).onUnshareScreen();
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener1, "theSessionId");
signalingMessageReceiver.addListener(mockedCallParticipantMessageListener2, "theSessionId");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("unshareScreen");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
InOrder inOrder = inOrder(mockedCallParticipantMessageListener1, mockedCallParticipantMessageListener2);
inOrder.verify(mockedCallParticipantMessageListener1).onUnshareScreen();
inOrder.verify(mockedCallParticipantMessageListener2).onUnshareScreen();
}
}

View file

@ -0,0 +1,180 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.signaling;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
public class SignalingMessageReceiverLocalParticipantTest {
private SignalingMessageReceiver signalingMessageReceiver;
@Before
public void setUp() {
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
// protected methods.
signalingMessageReceiver = new SignalingMessageReceiver() {
};
}
@Test
public void testAddLocalParticipantMessageListenerWithNullListener() {
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener((SignalingMessageReceiver.LocalParticipantMessageListener) null);
});
}
@Test
public void testExternalSignalingLocalParticipantMessageSwitchTo() {
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "switchto");
eventMap.put("target", "room");
Map<String, Object> switchToMap = new HashMap<>();
switchToMap.put("roomid", "theToken");
eventMap.put("switchto", switchToMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedLocalParticipantMessageListener, only()).onSwitchTo("theToken");
}
@Test
public void testExternalSignalingLocalParticipantMessageAfterRemovingListener() {
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener);
signalingMessageReceiver.removeListener(mockedLocalParticipantMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "switchto");
eventMap.put("target", "room");
HashMap<String, Object> switchToMap = new HashMap<>();
switchToMap.put("roomid", "theToken");
eventMap.put("switchto", switchToMap);
signalingMessageReceiver.processEvent(eventMap);
verifyNoInteractions(mockedLocalParticipantMessageListener);
}
@Test
public void testExternalSignalingLocalParticipantMessageAfterRemovingSingleListenerOfSeveral() {
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener1 =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener2 =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener3 =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener1);
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener2);
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener3);
signalingMessageReceiver.removeListener(mockedLocalParticipantMessageListener2);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "switchto");
eventMap.put("target", "room");
HashMap<String, Object> switchToMap = new HashMap<>();
switchToMap.put("roomid", "theToken");
eventMap.put("switchto", switchToMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedLocalParticipantMessageListener1, only()).onSwitchTo("theToken");
verify(mockedLocalParticipantMessageListener3, only()).onSwitchTo("theToken");
verifyNoInteractions(mockedLocalParticipantMessageListener2);
}
@Test
public void testExternalSignalingLocalParticipantMessageAfterAddingListenerAgain() {
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener);
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "switchto");
eventMap.put("target", "room");
HashMap<String, Object> switchToMap = new HashMap<>();
switchToMap.put("roomid", "theToken");
eventMap.put("switchto", switchToMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedLocalParticipantMessageListener, only()).onSwitchTo("theToken");
}
@Test
public void testAddLocalParticipantMessageListenerWhenHandlingExternalSignalingLocalParticipantMessage() {
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener1 =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener2 =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener2);
return null;
}).when(mockedLocalParticipantMessageListener1).onSwitchTo("theToken");
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener1);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "switchto");
eventMap.put("target", "room");
HashMap<String, Object> switchToMap = new HashMap<>();
switchToMap.put("roomid", "theToken");
eventMap.put("switchto", switchToMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedLocalParticipantMessageListener1, only()).onSwitchTo("theToken");
verifyNoInteractions(mockedLocalParticipantMessageListener2);
}
@Test
public void testRemoveLocalParticipantMessageListenerWhenHandlingExternalSignalingLocalParticipantMessage() {
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener1 =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
SignalingMessageReceiver.LocalParticipantMessageListener mockedLocalParticipantMessageListener2 =
mock(SignalingMessageReceiver.LocalParticipantMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.removeListener(mockedLocalParticipantMessageListener2);
return null;
}).when(mockedLocalParticipantMessageListener1).onSwitchTo("theToken");
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener1);
signalingMessageReceiver.addListener(mockedLocalParticipantMessageListener2);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "switchto");
eventMap.put("target", "room");
HashMap<String, Object> switchToMap = new HashMap<>();
switchToMap.put("roomid", "theToken");
eventMap.put("switchto", switchToMap);
signalingMessageReceiver.processEvent(eventMap);
InOrder inOrder = inOrder(mockedLocalParticipantMessageListener1, mockedLocalParticipantMessageListener2);
inOrder.verify(mockedLocalParticipantMessageListener1).onSwitchTo("theToken");
inOrder.verify(mockedLocalParticipantMessageListener2).onSwitchTo("theToken");
}
}

View file

@ -0,0 +1,218 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.signaling;
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
public class SignalingMessageReceiverOfferTest {
private SignalingMessageReceiver signalingMessageReceiver;
@Before
public void setUp() {
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
// protected methods.
signalingMessageReceiver = new SignalingMessageReceiver() {
};
}
@Test
public void testAddOfferMessageListenerWithNullListener() {
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener((SignalingMessageReceiver.OfferMessageListener) null);
});
}
@Test
public void testOfferMessage() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
mock(SignalingMessageReceiver.OfferMessageListener.class);
signalingMessageReceiver.addListener(mockedOfferMessageListener);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedOfferMessageListener, only()).onOffer("theSessionId", "theRoomType", "theSdp", null);
}
@Test
public void testOfferMessageWithNick() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
mock(SignalingMessageReceiver.OfferMessageListener.class);
signalingMessageReceiver.addListener(mockedOfferMessageListener);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedOfferMessageListener, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
}
@Test
public void testOfferMessageAfterRemovingListener() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
mock(SignalingMessageReceiver.OfferMessageListener.class);
signalingMessageReceiver.addListener(mockedOfferMessageListener);
signalingMessageReceiver.removeListener(mockedOfferMessageListener);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verifyNoInteractions(mockedOfferMessageListener);
}
@Test
public void testOfferMessageAfterRemovingSingleListenerOfSeveral() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener1 =
mock(SignalingMessageReceiver.OfferMessageListener.class);
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener2 =
mock(SignalingMessageReceiver.OfferMessageListener.class);
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener3 =
mock(SignalingMessageReceiver.OfferMessageListener.class);
signalingMessageReceiver.addListener(mockedOfferMessageListener1);
signalingMessageReceiver.addListener(mockedOfferMessageListener2);
signalingMessageReceiver.addListener(mockedOfferMessageListener3);
signalingMessageReceiver.removeListener(mockedOfferMessageListener2);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedOfferMessageListener1, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
verify(mockedOfferMessageListener3, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
verifyNoInteractions(mockedOfferMessageListener2);
}
@Test
public void testOfferMessageAfterAddingListenerAgain() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
mock(SignalingMessageReceiver.OfferMessageListener.class);
signalingMessageReceiver.addListener(mockedOfferMessageListener);
signalingMessageReceiver.addListener(mockedOfferMessageListener);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedOfferMessageListener, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
}
@Test
public void testAddOfferMessageListenerWhenHandlingOffer() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener1 =
mock(SignalingMessageReceiver.OfferMessageListener.class);
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener2 =
mock(SignalingMessageReceiver.OfferMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.addListener(mockedOfferMessageListener2);
return null;
}).when(mockedOfferMessageListener1).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
signalingMessageReceiver.addListener(mockedOfferMessageListener1);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedOfferMessageListener1, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
verifyNoInteractions(mockedOfferMessageListener2);
}
@Test
public void testRemoveOfferMessageListenerWhenHandlingOffer() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener1 =
mock(SignalingMessageReceiver.OfferMessageListener.class);
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener2 =
mock(SignalingMessageReceiver.OfferMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.removeListener(mockedOfferMessageListener2);
return null;
}).when(mockedOfferMessageListener1).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
signalingMessageReceiver.addListener(mockedOfferMessageListener1);
signalingMessageReceiver.addListener(mockedOfferMessageListener2);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
InOrder inOrder = inOrder(mockedOfferMessageListener1, mockedOfferMessageListener2);
inOrder.verify(mockedOfferMessageListener1).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
inOrder.verify(mockedOfferMessageListener2).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
}
}

View file

@ -0,0 +1,461 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.signaling;
import com.nextcloud.talk.models.json.participants.Participant;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
public class SignalingMessageReceiverParticipantListTest {
private SignalingMessageReceiver signalingMessageReceiver;
@Before
public void setUp() {
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
// protected methods.
signalingMessageReceiver = new SignalingMessageReceiver() {
};
}
@Test
public void testAddParticipantListMessageListenerWithNullListener() {
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener((SignalingMessageReceiver.ParticipantListMessageListener) null);
});
}
@Test
public void testInternalSignalingParticipantListMessageUsersInRoom() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
List<Map<String, Object>> users = new ArrayList<>(2);
Map<String, Object> user1 = new HashMap<>();
user1.put("inCall", 7);
user1.put("lastPing", 4815);
user1.put("roomId", 108);
user1.put("sessionId", "theSessionId1");
user1.put("userId", "theUserId");
// If any of the following properties is set in any of the participants all the other participants in the
// message would have it too. But for test simplicity, and as it is not relevant for the processing, in this
// test they are included only in one of the participants.
user1.put("participantPermissions", 42);
user1.put("actorType", "federated_users");
user1.put("actorId", "theActorId");
users.add(user1);
Map<String, Object> user2 = new HashMap<>();
user2.put("inCall", 0);
user2.put("lastPing", 162342);
user2.put("roomId", 108);
user2.put("sessionId", "theSessionId2");
user2.put("userId", "");
users.add(user2);
signalingMessageReceiver.processUsersInRoom(users);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant1 = new Participant();
expectedParticipant1.setInCall(Participant.InCallFlags.IN_CALL | Participant.InCallFlags.WITH_AUDIO | Participant.InCallFlags.WITH_VIDEO);
expectedParticipant1.setLastPing(4815);
expectedParticipant1.setSessionId("theSessionId1");
expectedParticipant1.setUserId("theUserId");
expectedParticipant1.setActorType(Participant.ActorType.FEDERATED);
expectedParticipant1.setActorId("theActorId");
expectedParticipantList.add(expectedParticipant1);
Participant expectedParticipant2 = new Participant();
expectedParticipant2.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant2.setLastPing(162342);
expectedParticipant2.setSessionId("theSessionId2");
expectedParticipantList.add(expectedParticipant2);
verify(mockedParticipantListMessageListener, only()).onUsersInRoom(expectedParticipantList);
}
@Test
public void testInternalSignalingParticipantListMessageAfterRemovingListener() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
verifyNoInteractions(mockedParticipantListMessageListener);
}
@Test
public void testInternalSignalingParticipantListMessageAfterRemovingSingleListenerOfSeveral() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener3 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener3);
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant = new Participant();
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant.setLastPing(4815);
expectedParticipant.setSessionId("theSessionId");
expectedParticipantList.add(expectedParticipant);
verify(mockedParticipantListMessageListener1, only()).onUsersInRoom(expectedParticipantList);
verify(mockedParticipantListMessageListener3, only()).onUsersInRoom(expectedParticipantList);
verifyNoInteractions(mockedParticipantListMessageListener2);
}
@Test
public void testInternalSignalingParticipantListMessageAfterAddingListenerAgain() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant = new Participant();
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant.setLastPing(4815);
expectedParticipant.setSessionId("theSessionId");
expectedParticipantList.add(expectedParticipant);
verify(mockedParticipantListMessageListener, only()).onUsersInRoom(expectedParticipantList);
}
@Test
public void testAddParticipantListMessageListenerWhenHandlingInternalSignalingParticipantListMessage() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant = new Participant();
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant.setLastPing(4815);
expectedParticipant.setSessionId("theSessionId");
expectedParticipantList.add(expectedParticipant);
doAnswer((invocation) -> {
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
return null;
}).when(mockedParticipantListMessageListener1).onUsersInRoom(expectedParticipantList);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
verify(mockedParticipantListMessageListener1, only()).onUsersInRoom(expectedParticipantList);
verifyNoInteractions(mockedParticipantListMessageListener2);
}
@Test
public void testRemoveParticipantListMessageListenerWhenHandlingInternalSignalingParticipantListMessage() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
List<Participant> expectedParticipantList = new ArrayList<>();
Participant expectedParticipant = new Participant();
expectedParticipant.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant.setLastPing(4815);
expectedParticipant.setSessionId("theSessionId");
expectedParticipantList.add(expectedParticipant);
doAnswer((invocation) -> {
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
return null;
}).when(mockedParticipantListMessageListener1).onUsersInRoom(expectedParticipantList);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
List<Map<String, Object>> users = new ArrayList<>(1);
Map<String, Object> user = new HashMap<>();
user.put("inCall", 0);
user.put("lastPing", 4815);
user.put("roomId", 108);
user.put("sessionId", "theSessionId");
user.put("userId", "");
users.add(user);
signalingMessageReceiver.processUsersInRoom(users);
InOrder inOrder = inOrder(mockedParticipantListMessageListener1, mockedParticipantListMessageListener2);
inOrder.verify(mockedParticipantListMessageListener1).onUsersInRoom(expectedParticipantList);
inOrder.verify(mockedParticipantListMessageListener2).onUsersInRoom(expectedParticipantList);
}
@Test
public void testExternalSignalingParticipantListMessageParticipantsUpdate() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
Map<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
List<Map<String, Object>> users = new ArrayList<>(2);
Map<String, Object> user1 = new HashMap<>();
user1.put("inCall", 7);
user1.put("lastPing", 4815);
user1.put("sessionId", "theSessionId1");
user1.put("participantType", 3);
user1.put("userId", "theUserId");
// If any of the following properties is set in any of the participants all the other participants in the
// message would have it too. But for test simplicity, and as it is not relevant for the processing, in this
// test they are included only in one of the participants.
user1.put("nextcloudSessionId", "theNextcloudSessionId");
user1.put("participantPermissions", 42);
user1.put("actorType", "federated_users");
user1.put("actorId", "theActorId");
users.add(user1);
Map<String, Object> user2 = new HashMap<>();
user2.put("inCall", 0);
user2.put("lastPing", 162342);
user2.put("sessionId", "theSessionId2");
user2.put("participantType", 4);
users.add(user2);
updateMap.put("users", users);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
List<Participant> expectedParticipantList = new ArrayList<>(2);
Participant expectedParticipant1 = new Participant();
expectedParticipant1.setInCall(Participant.InCallFlags.IN_CALL | Participant.InCallFlags.WITH_AUDIO | Participant.InCallFlags.WITH_VIDEO);
expectedParticipant1.setLastPing(4815);
expectedParticipant1.setSessionId("theSessionId1");
expectedParticipant1.setType(Participant.ParticipantType.USER);
expectedParticipant1.setUserId("theUserId");
expectedParticipant1.setActorType(Participant.ActorType.FEDERATED);
expectedParticipant1.setActorId("theActorId");
expectedParticipantList.add(expectedParticipant1);
Participant expectedParticipant2 = new Participant();
expectedParticipant2.setInCall(Participant.InCallFlags.DISCONNECTED);
expectedParticipant2.setLastPing(162342);
expectedParticipant2.setSessionId("theSessionId2");
expectedParticipant2.setType(Participant.ParticipantType.GUEST);
expectedParticipantList.add(expectedParticipant2);
verify(mockedParticipantListMessageListener, only()).onParticipantsUpdate(expectedParticipantList);
}
@Test
public void testExternalSignalingParticipantListMessageAllParticipantsUpdate() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
Map<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("incall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedParticipantListMessageListener, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
}
@Test
public void testExternalSignalingParticipantListMessageAfterRemovingListener() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("incall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verifyNoInteractions(mockedParticipantListMessageListener);
}
@Test
public void testExternalSignalingParticipantListMessageAfterRemovingSingleListenerOfSeveral() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener3 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener3);
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("incall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedParticipantListMessageListener1, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
verify(mockedParticipantListMessageListener3, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
verifyNoInteractions(mockedParticipantListMessageListener2);
}
@Test
public void testExternalSignalingParticipantListMessageAfterAddingListenerAgain() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("incall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedParticipantListMessageListener, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
}
@Test
public void testAddParticipantListMessageListenerWhenHandlingExternalSignalingParticipantListMessage() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
return null;
}).when(mockedParticipantListMessageListener1).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("incall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
verify(mockedParticipantListMessageListener1, only()).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
verifyNoInteractions(mockedParticipantListMessageListener2);
}
@Test
public void testRemoveParticipantListMessageListenerWhenHandlingExternalSignalingParticipantListMessage() {
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener1 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
SignalingMessageReceiver.ParticipantListMessageListener mockedParticipantListMessageListener2 =
mock(SignalingMessageReceiver.ParticipantListMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.removeListener(mockedParticipantListMessageListener2);
return null;
}).when(mockedParticipantListMessageListener1).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener1);
signalingMessageReceiver.addListener(mockedParticipantListMessageListener2);
Map<String, Object> eventMap = new HashMap<>();
eventMap.put("type", "update");
eventMap.put("target", "participants");
HashMap<String, Object> updateMap = new HashMap<>();
updateMap.put("roomId", 108);
updateMap.put("all", true);
updateMap.put("incall", 0);
eventMap.put("update", updateMap);
signalingMessageReceiver.processEvent(eventMap);
InOrder inOrder = inOrder(mockedParticipantListMessageListener1, mockedParticipantListMessageListener2);
inOrder.verify(mockedParticipantListMessageListener1).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
inOrder.verify(mockedParticipantListMessageListener2).onAllParticipantsUpdate(Participant.InCallFlags.DISCONNECTED);
}
}

View file

@ -0,0 +1,122 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.signaling;
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
public class SignalingMessageReceiverTest {
private SignalingMessageReceiver signalingMessageReceiver;
@Before
public void setUp() {
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
// protected methods.
signalingMessageReceiver = new SignalingMessageReceiver() {
};
}
@Test
public void testOfferWithOfferAndWebRtcMessageListeners() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
mock(SignalingMessageReceiver.OfferMessageListener.class);
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedOfferMessageListener);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
InOrder inOrder = inOrder(mockedOfferMessageListener, mockedWebRtcMessageListener);
inOrder.verify(mockedOfferMessageListener).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
inOrder.verify(mockedWebRtcMessageListener).onOffer("theSdp", "theNick");
}
@Test
public void testAddWebRtcMessageListenerWhenHandlingOffer() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
mock(SignalingMessageReceiver.OfferMessageListener.class);
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
return null;
}).when(mockedOfferMessageListener).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
signalingMessageReceiver.addListener(mockedOfferMessageListener);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
InOrder inOrder = inOrder(mockedOfferMessageListener, mockedWebRtcMessageListener);
inOrder.verify(mockedOfferMessageListener).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
inOrder.verify(mockedWebRtcMessageListener).onOffer("theSdp", "theNick");
}
@Test
public void testRemoveWebRtcMessageListenerWhenHandlingOffer() {
SignalingMessageReceiver.OfferMessageListener mockedOfferMessageListener =
mock(SignalingMessageReceiver.OfferMessageListener.class);
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.removeListener(mockedWebRtcMessageListener);
return null;
}).when(mockedOfferMessageListener).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
signalingMessageReceiver.addListener(mockedOfferMessageListener);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedOfferMessageListener, only()).onOffer("theSessionId", "theRoomType", "theSdp", "theNick");
verifyNoInteractions(mockedWebRtcMessageListener);
}
}

View file

@ -0,0 +1,353 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.signaling;
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
import com.nextcloud.talk.models.json.signaling.NCMessagePayload;
import com.nextcloud.talk.models.json.signaling.NCSignalingMessage;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
public class SignalingMessageReceiverWebRtcTest {
private SignalingMessageReceiver signalingMessageReceiver;
@Before
public void setUp() {
// SignalingMessageReceiver is abstract to prevent direct instantiation without calling the appropriate
// protected methods.
signalingMessageReceiver = new SignalingMessageReceiver() {
};
}
@Test
public void testAddWebRtcMessageListenerWithNullListener() {
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener(null, "theSessionId", "theRoomType");
});
}
@Test
public void testAddWebRtcMessageListenerWithNullSessionId() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, null, "theRoomType");
});
}
@Test
public void testAddWebRtcMessageListenerWithNullRoomType() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
Assert.assertThrows(IllegalArgumentException.class, () -> {
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", null);
});
}
@Test
public void testWebRtcMessageOffer() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener, only()).onOffer("theSdp", null);
}
@Test
public void testWebRtcMessageOfferWithNick() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("offer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("offer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener, only()).onOffer("theSdp", "theNick");
}
@Test
public void testWebRtcMessageAnswer() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("answer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("answer");
messagePayload.setSdp("theSdp");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener, only()).onAnswer("theSdp", null);
}
@Test
public void testWebRtcMessageAnswerWithNick() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("answer");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
messagePayload.setType("answer");
messagePayload.setSdp("theSdp");
messagePayload.setNick("theNick");
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener, only()).onAnswer("theSdp", "theNick");
}
@Test
public void testWebRtcMessageCandidate() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("candidate");
signalingMessage.setRoomType("theRoomType");
NCMessagePayload messagePayload = new NCMessagePayload();
NCIceCandidate iceCandidate = new NCIceCandidate();
iceCandidate.setSdpMid("theSdpMid");
iceCandidate.setSdpMLineIndex(42);
iceCandidate.setCandidate("theSdp");
messagePayload.setIceCandidate(iceCandidate);
signalingMessage.setPayload(messagePayload);
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener, only()).onCandidate("theSdpMid", 42, "theSdp");
}
@Test
public void testWebRtcMessageEndOfCandidates() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener, only()).onEndOfCandidates();
}
@Test
public void testWebRtcMessageSeveralListenersSameFrom() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener1 =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener2 =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener1, "theSessionId", "theRoomType");
signalingMessageReceiver.addListener(mockedWebRtcMessageListener2, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener1, only()).onEndOfCandidates();
verify(mockedWebRtcMessageListener2, only()).onEndOfCandidates();
}
@Test
public void testWebRtcMessageNotMatchingSessionId() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("notMatchingSessionId");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verifyNoInteractions(mockedWebRtcMessageListener);
}
@Test
public void testWebRtcMessageNotMatchingRoomType() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("notMatchingRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verifyNoInteractions(mockedWebRtcMessageListener);
}
@Test
public void testWebRtcMessageAfterRemovingListener() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
signalingMessageReceiver.removeListener(mockedWebRtcMessageListener);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verifyNoInteractions(mockedWebRtcMessageListener);
}
@Test
public void testWebRtcMessageAfterRemovingSingleListenerOfSeveral() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener1 =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener2 =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener3 =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener1, "theSessionId", "theRoomType");
signalingMessageReceiver.addListener(mockedWebRtcMessageListener2, "theSessionId", "theRoomType");
signalingMessageReceiver.addListener(mockedWebRtcMessageListener3, "theSessionId", "theRoomType");
signalingMessageReceiver.removeListener(mockedWebRtcMessageListener2);
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener1, only()).onEndOfCandidates();
verify(mockedWebRtcMessageListener3, only()).onEndOfCandidates();
verifyNoInteractions(mockedWebRtcMessageListener2);
}
@Test
public void testWebRtcMessageAfterAddingListenerAgainForDifferentFrom() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId", "theRoomType");
signalingMessageReceiver.addListener(mockedWebRtcMessageListener, "theSessionId2", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verifyNoInteractions(mockedWebRtcMessageListener);
signalingMessage.setFrom("theSessionId2");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener, only()).onEndOfCandidates();
}
@Test
public void testAddWebRtcMessageListenerWhenHandlingWebRtcMessage() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener1 =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener2 =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.addListener(mockedWebRtcMessageListener2, "theSessionId", "theRoomType");
return null;
}).when(mockedWebRtcMessageListener1).onEndOfCandidates();
signalingMessageReceiver.addListener(mockedWebRtcMessageListener1, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
verify(mockedWebRtcMessageListener1, only()).onEndOfCandidates();
verifyNoInteractions(mockedWebRtcMessageListener2);
}
@Test
public void testRemoveWebRtcMessageListenerWhenHandlingWebRtcMessage() {
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener1 =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
SignalingMessageReceiver.WebRtcMessageListener mockedWebRtcMessageListener2 =
mock(SignalingMessageReceiver.WebRtcMessageListener.class);
doAnswer((invocation) -> {
signalingMessageReceiver.removeListener(mockedWebRtcMessageListener2);
return null;
}).when(mockedWebRtcMessageListener1).onEndOfCandidates();
signalingMessageReceiver.addListener(mockedWebRtcMessageListener1, "theSessionId", "theRoomType");
signalingMessageReceiver.addListener(mockedWebRtcMessageListener2, "theSessionId", "theRoomType");
NCSignalingMessage signalingMessage = new NCSignalingMessage();
signalingMessage.setFrom("theSessionId");
signalingMessage.setType("endOfCandidates");
signalingMessage.setRoomType("theRoomType");
signalingMessageReceiver.processSignalingMessage(signalingMessage);
InOrder inOrder = inOrder(mockedWebRtcMessageListener1, mockedWebRtcMessageListener2);
inOrder.verify(mockedWebRtcMessageListener1).onEndOfCandidates();
inOrder.verify(mockedWebRtcMessageListener2).onEndOfCandidates();
}
}

View file

@ -0,0 +1,20 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.test.fakes
import com.nextcloud.talk.models.domain.StartCallRecordingModel
import com.nextcloud.talk.models.domain.StopCallRecordingModel
import com.nextcloud.talk.repositories.callrecording.CallRecordingRepository
import io.reactivex.Observable
class FakeCallRecordingRepository : CallRecordingRepository {
override fun startRecording(roomToken: String) = Observable.just(StartCallRecordingModel(true))
override fun stopRecording(roomToken: String) = Observable.just(StopCallRecordingModel(true))
}

View file

@ -0,0 +1,37 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.test.fakes
import com.nextcloud.talk.models.domain.SearchMessageEntry
import com.nextcloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import io.reactivex.Observable
class FakeUnifiedSearchRepository : UnifiedSearchRepository {
lateinit var response: UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry>
var lastRequestedCursor = -1
override fun searchMessages(
searchTerm: String,
cursor: Int,
limit: Int
): Observable<UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry>> {
lastRequestedCursor = cursor
return Observable.just(response)
}
override fun searchInRoom(
roomToken: String,
searchTerm: String,
cursor: Int,
limit: Int
): Observable<UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry>> {
lastRequestedCursor = cursor
return Observable.just(response)
}
}

View file

@ -0,0 +1,90 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Samanwith KSN <samanwith21@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.utils
import com.nextcloud.talk.utils.bundle.BundleKeys
import junit.framework.TestCase.assertEquals
import org.junit.Test
class BundleKeysTest {
@Test
fun testBundleKeysValues() {
assertEquals("KEY_SELECTED_USERS", BundleKeys.KEY_SELECTED_USERS)
assertEquals("KEY_SELECTED_GROUPS", BundleKeys.KEY_SELECTED_GROUPS)
assertEquals("KEY_SELECTED_CIRCLES", BundleKeys.KEY_SELECTED_CIRCLES)
assertEquals("KEY_SELECTED_EMAILS", BundleKeys.KEY_SELECTED_EMAILS)
assertEquals("KEY_USERNAME", BundleKeys.KEY_USERNAME)
assertEquals("KEY_TOKEN", BundleKeys.KEY_TOKEN)
assertEquals("KEY_TRANSLATE_MESSAGE", BundleKeys.KEY_TRANSLATE_MESSAGE)
assertEquals("KEY_BASE_URL", BundleKeys.KEY_BASE_URL)
assertEquals("KEY_IS_ACCOUNT_IMPORT", BundleKeys.KEY_IS_ACCOUNT_IMPORT)
assertEquals("KEY_ORIGINAL_PROTOCOL", BundleKeys.KEY_ORIGINAL_PROTOCOL)
assertEquals("KEY_OPERATION_CODE", BundleKeys.KEY_OPERATION_CODE)
assertEquals("KEY_APP_ITEM_PACKAGE_NAME", BundleKeys.KEY_APP_ITEM_PACKAGE_NAME)
assertEquals("KEY_APP_ITEM_NAME", BundleKeys.KEY_APP_ITEM_NAME)
assertEquals("KEY_CONVERSATION_PASSWORD", BundleKeys.KEY_CONVERSATION_PASSWORD)
assertEquals("KEY_ROOM_TOKEN", BundleKeys.KEY_ROOM_TOKEN)
assertEquals("KEY_ROOM_ONE_TO_ONE", BundleKeys.KEY_ROOM_ONE_TO_ONE)
assertEquals("KEY_NEW_CONVERSATION", BundleKeys.KEY_NEW_CONVERSATION)
assertEquals("KEY_ADD_PARTICIPANTS", BundleKeys.KEY_ADD_PARTICIPANTS)
assertEquals("KEY_EXISTING_PARTICIPANTS", BundleKeys.KEY_EXISTING_PARTICIPANTS)
assertEquals("KEY_CALL_URL", BundleKeys.KEY_CALL_URL)
assertEquals("KEY_NEW_ROOM_NAME", BundleKeys.KEY_NEW_ROOM_NAME)
assertEquals("KEY_MODIFIED_BASE_URL", BundleKeys.KEY_MODIFIED_BASE_URL)
assertEquals("KEY_NOTIFICATION_SUBJECT", BundleKeys.KEY_NOTIFICATION_SUBJECT)
assertEquals("KEY_NOTIFICATION_SIGNATURE", BundleKeys.KEY_NOTIFICATION_SIGNATURE)
assertEquals("KEY_INTERNAL_USER_ID", BundleKeys.KEY_INTERNAL_USER_ID)
assertEquals("KEY_CONVERSATION_TYPE", BundleKeys.KEY_CONVERSATION_TYPE)
assertEquals("KEY_INVITED_PARTICIPANTS", BundleKeys.KEY_INVITED_PARTICIPANTS)
assertEquals("KEY_INVITED_CIRCLE", BundleKeys.KEY_INVITED_CIRCLE)
assertEquals("KEY_INVITED_GROUP", BundleKeys.KEY_INVITED_GROUP)
assertEquals("KEY_INVITED_EMAIL", BundleKeys.KEY_INVITED_EMAIL)
}
@Test
fun testBundleKeysValues2() {
assertEquals("KEY_CONVERSATION_NAME", BundleKeys.KEY_CONVERSATION_NAME)
assertEquals("KEY_RECORDING_STATE", BundleKeys.KEY_RECORDING_STATE)
assertEquals("KEY_CALL_VOICE_ONLY", BundleKeys.KEY_CALL_VOICE_ONLY)
assertEquals("KEY_CALL_WITHOUT_NOTIFICATION", BundleKeys.KEY_CALL_WITHOUT_NOTIFICATION)
assertEquals("KEY_FROM_NOTIFICATION_START_CALL", BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)
assertEquals("KEY_ROOM_ID", BundleKeys.KEY_ROOM_ID)
assertEquals("KEY_ARE_CALL_SOUNDS", BundleKeys.KEY_ARE_CALL_SOUNDS)
assertEquals("KEY_FILE_PATHS", BundleKeys.KEY_FILE_PATHS)
assertEquals("KEY_ACCOUNT", BundleKeys.KEY_ACCOUNT)
assertEquals("KEY_FILE_ID", BundleKeys.KEY_FILE_ID)
assertEquals("KEY_NOTIFICATION_ID", BundleKeys.KEY_NOTIFICATION_ID)
assertEquals("KEY_NOTIFICATION_TIMESTAMP", BundleKeys.KEY_NOTIFICATION_TIMESTAMP)
assertEquals("KEY_SHARED_TEXT", BundleKeys.KEY_SHARED_TEXT)
assertEquals("KEY_GEOCODING_QUERY", BundleKeys.KEY_GEOCODING_QUERY)
assertEquals("KEY_META_DATA", BundleKeys.KEY_META_DATA)
assertEquals("KEY_FORWARD_MSG_FLAG", BundleKeys.KEY_FORWARD_MSG_FLAG)
assertEquals("KEY_FORWARD_MSG_TEXT", BundleKeys.KEY_FORWARD_MSG_TEXT)
assertEquals("KEY_FORWARD_HIDE_SOURCE_ROOM", BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM)
assertEquals("KEY_SYSTEM_NOTIFICATION_ID", BundleKeys.KEY_SYSTEM_NOTIFICATION_ID)
assertEquals("KEY_MESSAGE_ID", BundleKeys.KEY_MESSAGE_ID)
assertEquals("KEY_MIME_TYPE_FILTER", BundleKeys.KEY_MIME_TYPE_FILTER)
assertEquals(
"KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO",
BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_AUDIO
)
assertEquals(
"KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO",
BundleKeys.KEY_PARTICIPANT_PERMISSION_CAN_PUBLISH_VIDEO
)
assertEquals("KEY_IS_MODERATOR", BundleKeys.KEY_IS_MODERATOR)
assertEquals("KEY_SWITCH_TO_ROOM", BundleKeys.KEY_SWITCH_TO_ROOM)
assertEquals("KEY_START_CALL_AFTER_ROOM_SWITCH", BundleKeys.KEY_START_CALL_AFTER_ROOM_SWITCH)
assertEquals("KEY_IS_BREAKOUT_ROOM", BundleKeys.KEY_IS_BREAKOUT_ROOM)
assertEquals("KEY_NOTIFICATION_RESTRICT_DELETION", BundleKeys.KEY_NOTIFICATION_RESTRICT_DELETION)
assertEquals("KEY_DISMISS_RECORDING_URL", BundleKeys.KEY_DISMISS_RECORDING_URL)
assertEquals("KEY_SHARE_RECORDING_TO_CHAT_URL", BundleKeys.KEY_SHARE_RECORDING_TO_CHAT_URL)
assertEquals("KEY_GEOCODING_RESULT", BundleKeys.KEY_GEOCODING_RESULT)
assertEquals("ADD_ADDITIONAL_ACCOUNT", BundleKeys.ADD_ADDITIONAL_ACCOUNT)
assertEquals("SAVED_TRANSLATED_MESSAGE", BundleKeys.SAVED_TRANSLATED_MESSAGE)
}
}

View file

@ -0,0 +1,60 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2017-2019 Mario Danic <mario@lovelyhq.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.utils;
import android.app.NotificationManager;
import android.content.Context;
import android.media.AudioManager;
import android.os.Build;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.junit.Assert.assertFalse;
import static org.mockito.Mockito.when;
public class DoNotDisturbUtilsTest {
@Mock
private Context context;
@Mock
private NotificationManager notificationManager;
@Mock
private AudioManager audioManager;
@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
when(context.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(notificationManager);
when(context.getSystemService(Context.AUDIO_SERVICE)).thenReturn(audioManager);
}
@Test
public void shouldPlaySound_givenAndroidMAndInterruptionFilterNone_assertReturnsFalse() {
DoNotDisturbUtils.INSTANCE.setTestingBuildVersion(Build.VERSION_CODES.M);
when(notificationManager.getCurrentInterruptionFilter()).thenReturn(NotificationManager.INTERRUPTION_FILTER_NONE);
when(audioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
assertFalse("shouldPlaySound incorrectly returned true",
DoNotDisturbUtils.INSTANCE.shouldPlaySound(context));
}
@Test
public void shouldPlaySound_givenRingerModeNotNormal_assertReturnsFalse() throws Exception {
DoNotDisturbUtils.INSTANCE.setTestingBuildVersion(Build.VERSION_CODES.LOLLIPOP);
when(audioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
assertFalse("shouldPlaySound incorrectly returned true",
DoNotDisturbUtils.INSTANCE.shouldPlaySound(context));
}
}

View file

@ -0,0 +1,97 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Tim Krüger <t@timkrueger.me>
* SPDX-FileCopyrightText: 2022 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.models.domain.ConversationModel
import com.nextcloud.talk.models.json.capabilities.SpreedCapability
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.ConversationEnums
import com.nextcloud.talk.models.json.participants.Participant
import junit.framework.TestCase
import org.junit.Test
class ParticipantPermissionsTest : TestCase() {
@Test
fun test_areFlagsSet() {
val spreedCapability = SpreedCapability()
val conversation = createConversation()
conversation.permissions = ParticipantPermissions.PUBLISH_SCREEN or
ParticipantPermissions.JOIN_CALL or
ParticipantPermissions.DEFAULT
val user = User()
user.id = 1
val attendeePermissions =
ParticipantPermissions(
spreedCapability,
ConversationModel.mapToConversationModel(conversation, user)
)
assert(attendeePermissions.canPublishScreen)
assert(attendeePermissions.canJoinCall)
assert(attendeePermissions.isDefault)
assertFalse(attendeePermissions.isCustom)
assertFalse(attendeePermissions.canStartCall())
assertFalse(attendeePermissions.canIgnoreLobby())
assertTrue(attendeePermissions.canPublishAudio())
assertTrue(attendeePermissions.canPublishVideo())
}
private fun createConversation() =
Conversation(
token = "test",
name = "test",
displayName = "test",
description = "test",
type = ConversationEnums.ConversationType.DUMMY,
lastPing = 1,
participantType = Participant.ParticipantType.DUMMY,
hasPassword = true,
sessionId = "test",
actorId = "test",
actorType = "test",
password = "test",
favorite = false,
lastActivity = 1,
unreadMessages = 1,
unreadMention = false,
lastMessage = null,
objectType = ConversationEnums.ObjectType.DEFAULT,
notificationLevel = ConversationEnums.NotificationLevel.ALWAYS,
conversationReadOnlyState = ConversationEnums.ConversationReadOnlyState.CONVERSATION_READ_WRITE,
lobbyState = ConversationEnums.LobbyState.LOBBY_STATE_ALL_PARTICIPANTS,
lobbyTimer = 1,
lastReadMessage = 1,
lastCommonReadMessage = 1,
hasCall = true,
callFlag = 1,
canStartCall = false,
canLeaveConversation = true,
canDeleteConversation = true,
unreadMentionDirect = true,
notificationCalls = 1,
permissions = 1,
messageExpiration = 1,
status = "test",
statusIcon = "test",
statusMessage = "test",
statusClearAt = 1,
callRecording = 1,
avatarVersion = "test",
hasCustomAvatar = true,
callStartTime = 1,
recordingConsentRequired = 1,
remoteServer = "",
remoteToken = ""
)
}

View file

@ -0,0 +1,42 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2023 Samanwith KSN <samanwith21@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.utils
import com.nextcloud.talk.data.user.model.User
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
class UserIdUtilsTest {
@Mock
private lateinit var user: User
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
}
@Test
fun testGetIdForUser_if_userIsNull_returnsNoId() {
Mockito.`when`(user.id).thenReturn(null)
val result = UserIdUtils.getIdForUser(user)
Assert.assertEquals("The id is NO_ID when user is null", UserIdUtils.NO_ID, result)
}
@Test
fun testGetIdForUser_if_userIdIsSet_returnsUserId() {
val expectedId: Long = 12345
Mockito.`when`(user.id).thenReturn(expectedId)
val result = UserIdUtils.getIdForUser(user)
Assert.assertEquals("The id is correct user id is not null", expectedId, result)
}
}

View file

@ -0,0 +1,40 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
* SPDX-FileCopyrightText: 2023 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.viewmodels
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import io.reactivex.android.plugins.RxAndroidPlugins
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
import org.junit.BeforeClass
import org.junit.Rule
open class AbstractViewModelTest {
@get:Rule
val instantExecutorRule = InstantTaskExecutorRule()
companion object {
@JvmStatic
@BeforeClass
fun setUpClass() {
RxJavaPlugins.setIoSchedulerHandler {
Schedulers.trampoline()
}
RxJavaPlugins.setComputationSchedulerHandler {
Schedulers.trampoline()
}
RxJavaPlugins.setNewThreadSchedulerHandler {
Schedulers.trampoline()
}
RxAndroidPlugins.setInitMainThreadSchedulerHandler {
Schedulers.trampoline()
}
}
}
}

View file

@ -0,0 +1,114 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.viewmodels
import com.nextcloud.talk.test.fakes.FakeCallRecordingRepository
import com.vividsolutions.jts.util.Assert
import org.junit.Before
import org.junit.Test
import org.mockito.MockitoAnnotations
class CallRecordingViewModelTest : AbstractViewModelTest() {
private val repository = FakeCallRecordingRepository()
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
}
@Test
fun testCallRecordingViewModel_clickStartRecord() {
val viewModel = CallRecordingViewModel(repository)
viewModel.setData("foo")
viewModel.clickRecordButton()
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingStartingState)
// fake to execute setRecordingState which would be triggered by signaling message
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTED_VIDEO_CODE)
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingStartedState)
}
@Test
fun testCallRecordingViewModel_clickStopRecord() {
val viewModel = CallRecordingViewModel(repository)
viewModel.setData("foo")
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTED_VIDEO_CODE)
Assert.equals(true, (viewModel.viewState.value as CallRecordingViewModel.RecordingStartedState).showStartedInfo)
viewModel.clickRecordButton()
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingConfirmStopState)
viewModel.stopRecording()
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingStoppedState)
}
@Test
fun testCallRecordingViewModel_keepConfirmState() {
val viewModel = CallRecordingViewModel(repository)
viewModel.setData("foo")
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTED_VIDEO_CODE)
Assert.equals(true, (viewModel.viewState.value as CallRecordingViewModel.RecordingStartedState).showStartedInfo)
viewModel.clickRecordButton()
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingConfirmStopState)
viewModel.clickRecordButton()
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingConfirmStopState)
}
@Test
fun testCallRecordingViewModel_continueRecordingWhenDismissStopDialog() {
val viewModel = CallRecordingViewModel(repository)
viewModel.setData("foo")
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTED_VIDEO_CODE)
viewModel.clickRecordButton()
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingConfirmStopState)
viewModel.dismissStopRecording()
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingStartedState)
Assert.equals(
false,
(viewModel.viewState.value as CallRecordingViewModel.RecordingStartedState).showStartedInfo
)
}
@Test
fun testSetRecordingStateDirectly() {
val viewModel = CallRecordingViewModel(repository)
viewModel.setData("foo")
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STOPPED_CODE)
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingStoppedState)
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTED_AUDIO_CODE)
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingStartedState)
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTED_VIDEO_CODE)
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingStartedState)
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTING_AUDIO_CODE)
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingStartingState)
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_STARTING_VIDEO_CODE)
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingStartingState)
viewModel.setRecordingState(CallRecordingViewModel.RECORDING_FAILED_CODE)
Assert.isTrue(viewModel.viewState.value is CallRecordingViewModel.RecordingErrorState)
}
}

View file

@ -0,0 +1,75 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Samanwith KSN <samanwith21@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.webrtc
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.mock
import org.mockito.Mockito.verify
class DataChannelMessageNotifierTest {
private lateinit var notifier: DataChannelMessageNotifier
private lateinit var listener: PeerConnectionWrapper.DataChannelMessageListener
@Before
fun setUp() {
notifier = DataChannelMessageNotifier()
listener = mock(PeerConnectionWrapper.DataChannelMessageListener::class.java)
}
@Test
fun testAddListener() {
notifier.addListener(listener)
assertTrue(notifier.dataChannelMessageListeners.contains(listener))
}
@Test
fun testRemoveListener() {
notifier.addListener(listener)
notifier.removeListener(listener)
assertFalse(notifier.dataChannelMessageListeners.contains(listener))
}
@Test
fun testNotifyAudioOn() {
notifier.addListener(listener)
notifier.notifyAudioOn()
verify(listener).onAudioOn()
}
@Test
fun testNotifyAudioOff() {
notifier.addListener(listener)
notifier.notifyAudioOff()
verify(listener).onAudioOff()
}
@Test
fun testNotifyVideoOn() {
notifier.addListener(listener)
notifier.notifyVideoOn()
verify(listener).onVideoOn()
}
@Test
fun testNotifyVideoOff() {
notifier.addListener(listener)
notifier.notifyVideoOff()
verify(listener).onVideoOff()
}
@Test
fun testNotifyNickChanged() {
notifier.addListener(listener)
val newNick = "NewNick"
notifier.notifyNickChanged(newNick)
verify(listener).onNickChanged(newNick)
}
}

View file

@ -0,0 +1,27 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Samanwith KSN <samanwith21@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.webrtc
import org.junit.Assert
import org.junit.Test
class GlobalsTest {
@Test
fun testRoomToken() {
Assert.assertEquals("roomToken", Globals.ROOM_TOKEN)
}
@Test
fun testTargetParticipants() {
Assert.assertEquals("participants", Globals.TARGET_PARTICIPANTS)
}
@Test
fun testTargetRoom() {
Assert.assertEquals("room", Globals.TARGET_ROOM)
}
}

View file

@ -0,0 +1,68 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Samanwith KSN <samanwith21@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.webrtc
import com.nextcloud.talk.webrtc.PeerConnectionWrapper.PeerConnectionObserver
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
import org.webrtc.MediaStream
import org.webrtc.PeerConnection
class PeerConnectionNotifierTest {
private var notifier: PeerConnectionNotifier? = null
private var observer1: PeerConnectionObserver? = null
private var observer2: PeerConnectionObserver? = null
private var mockStream: MediaStream? = null
@Before
fun setUp() {
notifier = PeerConnectionNotifier()
observer1 = Mockito.mock(PeerConnectionObserver::class.java)
observer2 = Mockito.mock(PeerConnectionObserver::class.java)
mockStream = Mockito.mock(MediaStream::class.java)
}
@Test
fun testAddObserver() {
notifier!!.addObserver(observer1)
notifier!!.notifyStreamAdded(mockStream)
Mockito.verify(observer1)?.onStreamAdded(mockStream)
Mockito.verify(observer2, Mockito.never())?.onStreamAdded(mockStream)
}
@Test
fun testRemoveObserver() {
notifier!!.addObserver(observer1)
notifier!!.addObserver(observer2)
notifier!!.removeObserver(observer1)
notifier!!.notifyStreamAdded(mockStream)
Mockito.verify(observer1, Mockito.never())?.onStreamAdded(mockStream)
Mockito.verify(observer2)?.onStreamAdded(mockStream)
}
@Test
fun testNotifyStreamAdded() {
notifier!!.addObserver(observer1)
notifier!!.notifyStreamAdded(mockStream)
Mockito.verify(observer1)?.onStreamAdded(mockStream)
}
@Test
fun testNotifyStreamRemoved() {
notifier!!.addObserver(observer1)
notifier!!.notifyStreamRemoved(mockStream)
Mockito.verify(observer1)?.onStreamRemoved(mockStream)
}
@Test
fun testNotifyIceConnectionStateChanged() {
notifier!!.addObserver(observer1)
notifier!!.notifyIceConnectionStateChanged(PeerConnection.IceConnectionState.CONNECTED)
Mockito.verify(observer1)?.onIceConnectionStateChanged(PeerConnection.IceConnectionState.CONNECTED)
}
}

View file

@ -0,0 +1,760 @@
/*
* Nextcloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package com.nextcloud.talk.webrtc
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.signaling.SignalingMessageReceiver
import com.nextcloud.talk.signaling.SignalingMessageSender
import com.nextcloud.talk.webrtc.PeerConnectionWrapper.DataChannelMessageListener
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatcher
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.argThat
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito
import org.mockito.Mockito.atLeast
import org.mockito.Mockito.atMostOnce
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.never
import org.mockito.invocation.InvocationOnMock
import org.mockito.stubbing.Answer
import org.webrtc.DataChannel
import org.webrtc.MediaConstraints
import org.webrtc.PeerConnection
import org.webrtc.PeerConnectionFactory
import java.nio.ByteBuffer
import java.util.HashMap
import kotlin.concurrent.thread
@Suppress("LongMethod", "TooGenericExceptionCaught")
class PeerConnectionWrapperTest {
private var peerConnectionWrapper: PeerConnectionWrapper? = null
private var mockedPeerConnection: PeerConnection? = null
private var mockedPeerConnectionFactory: PeerConnectionFactory? = null
private var mockedSignalingMessageReceiver: SignalingMessageReceiver? = null
private var mockedSignalingMessageSender: SignalingMessageSender? = null
/**
* Helper answer for DataChannel methods.
*/
private class ReturnValueOrThrowIfDisposed<T>(val value: T) : Answer<T> {
override fun answer(currentInvocation: InvocationOnMock): T {
if (Mockito.mockingDetails(currentInvocation.mock).invocations.find {
it!!.method.name === "dispose"
} !== null
) {
throw IllegalStateException("DataChannel has been disposed")
}
return value
}
}
/**
* Helper matcher for DataChannelMessages.
*/
private inner class MatchesDataChannelMessage(private val expectedDataChannelMessage: DataChannelMessage) :
ArgumentMatcher<DataChannel.Buffer> {
override fun matches(buffer: DataChannel.Buffer): Boolean {
// DataChannel.Buffer does not implement "equals", so the comparison needs to be done on the ByteBuffer
// instead.
return dataChannelMessageToBuffer(expectedDataChannelMessage).data.equals(buffer.data)
}
}
private fun dataChannelMessageToBuffer(dataChannelMessage: DataChannelMessage) =
DataChannel.Buffer(
ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).toByteArray()),
false
)
@Before
fun setUp() {
mockedPeerConnection = Mockito.mock(PeerConnection::class.java)
mockedPeerConnectionFactory = Mockito.mock(PeerConnectionFactory::class.java)
mockedSignalingMessageReceiver = Mockito.mock(SignalingMessageReceiver::class.java)
mockedSignalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
}
@Test
fun testSendDataChannelMessage() {
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
any(PeerConnection.Observer::class.java)
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenReturn("status")
Mockito.`when`(mockedStatusDataChannel.state()).thenReturn(DataChannel.State.OPEN)
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
peerConnectionWrapper!!.send(DataChannelMessage("the-message-type"))
Mockito.verify(mockedStatusDataChannel).send(
argThat(MatchesDataChannelMessage(DataChannelMessage("the-message-type")))
)
}
@Test
fun testSendDataChannelMessageWithOpenRemoteDataChannel() {
val peerConnectionObserverArgumentCaptor: ArgumentCaptor<PeerConnection.Observer> =
ArgumentCaptor.forClass(PeerConnection.Observer::class.java)
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
peerConnectionObserverArgumentCaptor.capture()
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenReturn("status")
Mockito.`when`(mockedStatusDataChannel.state()).thenReturn(DataChannel.State.OPEN)
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
val mockedRandomIdDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedRandomIdDataChannel.label()).thenReturn("random-id")
Mockito.`when`(mockedRandomIdDataChannel.state()).thenReturn(DataChannel.State.OPEN)
peerConnectionObserverArgumentCaptor.value.onDataChannel(mockedRandomIdDataChannel)
peerConnectionWrapper!!.send(DataChannelMessage("the-message-type"))
Mockito.verify(mockedStatusDataChannel).send(
argThat(MatchesDataChannelMessage(DataChannelMessage("the-message-type")))
)
Mockito.verify(mockedRandomIdDataChannel, never()).send(any())
}
@Test
fun testSendDataChannelMessageBeforeOpeningDataChannel() {
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
any(PeerConnection.Observer::class.java)
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenReturn("status")
Mockito.`when`(mockedStatusDataChannel.state()).thenReturn(DataChannel.State.CONNECTING)
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
val statusDataChannelObserverArgumentCaptor: ArgumentCaptor<DataChannel.Observer> =
ArgumentCaptor.forClass(DataChannel.Observer::class.java)
doNothing().`when`(mockedStatusDataChannel).registerObserver(statusDataChannelObserverArgumentCaptor.capture())
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
peerConnectionWrapper!!.send(DataChannelMessage("the-message-type"))
peerConnectionWrapper!!.send(DataChannelMessage("another-message-type"))
Mockito.verify(mockedStatusDataChannel, never()).send(any())
Mockito.`when`(mockedStatusDataChannel.state()).thenReturn(DataChannel.State.OPEN)
statusDataChannelObserverArgumentCaptor.value.onStateChange()
Mockito.verify(mockedStatusDataChannel).send(
argThat(MatchesDataChannelMessage(DataChannelMessage("the-message-type")))
)
Mockito.verify(mockedStatusDataChannel).send(
argThat(MatchesDataChannelMessage(DataChannelMessage("another-message-type")))
)
}
@Test
fun testSendDataChannelMessageBeforeOpeningDataChannelWithDifferentThreads() {
// A brute force approach is used to test race conditions between different threads just repeating the test
// several times. Due to this the test passing could be a false positive, as it could have been a matter of
// luck, but even if the test may wrongly pass sometimes it is better than nothing (although, in general, with
// that number of reruns, it fails when it should).
for (i in 1..1000) {
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
any(PeerConnection.Observer::class.java)
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenReturn("status")
Mockito.`when`(mockedStatusDataChannel.state()).thenReturn(DataChannel.State.CONNECTING)
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
val statusDataChannelObserverArgumentCaptor: ArgumentCaptor<DataChannel.Observer> =
ArgumentCaptor.forClass(DataChannel.Observer::class.java)
doNothing().`when`(mockedStatusDataChannel)
.registerObserver(statusDataChannelObserverArgumentCaptor.capture())
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
val dataChannelMessageCount = 5
val sendThread = thread {
for (j in 1..dataChannelMessageCount) {
peerConnectionWrapper!!.send(DataChannelMessage("the-message-type-$j"))
}
}
// Exceptions thrown in threads are not propagated to the main thread, so it needs to be explicitly done
// (for example, for ConcurrentModificationExceptions when iterating over the data channel messages).
var exceptionOnStateChange: Exception? = null
val openDataChannelThread = thread {
Mockito.`when`(mockedStatusDataChannel.state()).thenReturn(DataChannel.State.OPEN)
try {
statusDataChannelObserverArgumentCaptor.value.onStateChange()
} catch (e: Exception) {
exceptionOnStateChange = e
}
}
sendThread.join()
openDataChannelThread.join()
if (exceptionOnStateChange !== null) {
throw exceptionOnStateChange!!
}
val inOrder = inOrder(mockedStatusDataChannel)
for (j in 1..dataChannelMessageCount) {
inOrder.verify(mockedStatusDataChannel).send(
argThat(MatchesDataChannelMessage(DataChannelMessage("the-message-type-$j")))
)
}
}
}
@Test
fun testReceiveDataChannelMessage() {
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
any(PeerConnection.Observer::class.java)
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenReturn("status")
Mockito.`when`(mockedStatusDataChannel.state()).thenReturn(DataChannel.State.OPEN)
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
val statusDataChannelObserverArgumentCaptor: ArgumentCaptor<DataChannel.Observer> =
ArgumentCaptor.forClass(DataChannel.Observer::class.java)
doNothing().`when`(mockedStatusDataChannel).registerObserver(statusDataChannelObserverArgumentCaptor.capture())
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
val mockedDataChannelMessageListener = Mockito.mock(DataChannelMessageListener::class.java)
peerConnectionWrapper!!.addListener(mockedDataChannelMessageListener)
// The payload must be a map to be able to serialize it and, therefore, generate the data that would have been
// received from another participant, so it is not possible to test receiving the nick as a String payload.
val payloadMap = HashMap<String, String>()
payloadMap["name"] = "the-nick-in-map"
statusDataChannelObserverArgumentCaptor.value.onMessage(
dataChannelMessageToBuffer(DataChannelMessage("nickChanged", null, payloadMap))
)
Mockito.verify(mockedDataChannelMessageListener).onNickChanged("the-nick-in-map")
Mockito.verifyNoMoreInteractions(mockedDataChannelMessageListener)
statusDataChannelObserverArgumentCaptor.value.onMessage(
dataChannelMessageToBuffer(DataChannelMessage("audioOn"))
)
Mockito.verify(mockedDataChannelMessageListener).onAudioOn()
Mockito.verifyNoMoreInteractions(mockedDataChannelMessageListener)
statusDataChannelObserverArgumentCaptor.value.onMessage(
dataChannelMessageToBuffer(DataChannelMessage("audioOff"))
)
Mockito.verify(mockedDataChannelMessageListener).onAudioOff()
Mockito.verifyNoMoreInteractions(mockedDataChannelMessageListener)
statusDataChannelObserverArgumentCaptor.value.onMessage(
dataChannelMessageToBuffer(DataChannelMessage("videoOn"))
)
Mockito.verify(mockedDataChannelMessageListener).onVideoOn()
Mockito.verifyNoMoreInteractions(mockedDataChannelMessageListener)
statusDataChannelObserverArgumentCaptor.value.onMessage(
dataChannelMessageToBuffer(DataChannelMessage("videoOff"))
)
Mockito.verify(mockedDataChannelMessageListener).onVideoOff()
Mockito.verifyNoMoreInteractions(mockedDataChannelMessageListener)
}
@Test
fun testReceiveDataChannelMessageWithOpenRemoteDataChannel() {
val peerConnectionObserverArgumentCaptor: ArgumentCaptor<PeerConnection.Observer> =
ArgumentCaptor.forClass(PeerConnection.Observer::class.java)
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
peerConnectionObserverArgumentCaptor.capture()
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenReturn("status")
Mockito.`when`(mockedStatusDataChannel.state()).thenReturn(DataChannel.State.OPEN)
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
val statusDataChannelObserverArgumentCaptor: ArgumentCaptor<DataChannel.Observer> =
ArgumentCaptor.forClass(DataChannel.Observer::class.java)
doNothing().`when`(mockedStatusDataChannel).registerObserver(statusDataChannelObserverArgumentCaptor.capture())
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
val randomIdDataChannelObserverArgumentCaptor: ArgumentCaptor<DataChannel.Observer> =
ArgumentCaptor.forClass(DataChannel.Observer::class.java)
val mockedRandomIdDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedRandomIdDataChannel.label()).thenReturn("random-id")
Mockito.`when`(mockedRandomIdDataChannel.state()).thenReturn(DataChannel.State.OPEN)
doNothing().`when`(mockedRandomIdDataChannel).registerObserver(
randomIdDataChannelObserverArgumentCaptor.capture()
)
peerConnectionObserverArgumentCaptor.value.onDataChannel(mockedRandomIdDataChannel)
val mockedDataChannelMessageListener = Mockito.mock(DataChannelMessageListener::class.java)
peerConnectionWrapper!!.addListener(mockedDataChannelMessageListener)
statusDataChannelObserverArgumentCaptor.value.onMessage(
dataChannelMessageToBuffer(DataChannelMessage("audioOn"))
)
Mockito.verify(mockedDataChannelMessageListener).onAudioOn()
Mockito.verifyNoMoreInteractions(mockedDataChannelMessageListener)
randomIdDataChannelObserverArgumentCaptor.value.onMessage(
dataChannelMessageToBuffer(DataChannelMessage("audioOff"))
)
Mockito.verify(mockedDataChannelMessageListener).onAudioOff()
Mockito.verifyNoMoreInteractions(mockedDataChannelMessageListener)
}
@Test
fun testRemovePeerConnectionWithOpenRemoteDataChannel() {
val peerConnectionObserverArgumentCaptor: ArgumentCaptor<PeerConnection.Observer> =
ArgumentCaptor.forClass(PeerConnection.Observer::class.java)
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
peerConnectionObserverArgumentCaptor.capture()
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenReturn("status")
Mockito.`when`(mockedStatusDataChannel.state()).thenReturn(DataChannel.State.OPEN)
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
val mockedRandomIdDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedRandomIdDataChannel.label()).thenReturn("random-id")
Mockito.`when`(mockedRandomIdDataChannel.state()).thenReturn(DataChannel.State.OPEN)
peerConnectionObserverArgumentCaptor.value.onDataChannel(mockedRandomIdDataChannel)
peerConnectionWrapper!!.removePeerConnection()
Mockito.verify(mockedStatusDataChannel).dispose()
Mockito.verify(mockedRandomIdDataChannel).dispose()
}
@Test
fun testRemovePeerConnectionWhileAddingRemoteDataChannelsWithDifferentThreads() {
// A brute force approach is used to test race conditions between different threads just repeating the test
// several times. Due to this the test passing could be a false positive, as it could have been a matter of
// luck, but even if the test may wrongly pass sometimes it is better than nothing (although, in general, with
// that number of reruns, it fails when it should).
for (i in 1..1000) {
val peerConnectionObserverArgumentCaptor: ArgumentCaptor<PeerConnection.Observer> =
ArgumentCaptor.forClass(PeerConnection.Observer::class.java)
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
peerConnectionObserverArgumentCaptor.capture()
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenAnswer(ReturnValueOrThrowIfDisposed("status"))
Mockito.`when`(mockedStatusDataChannel.state()).thenAnswer(
ReturnValueOrThrowIfDisposed(DataChannel.State.OPEN)
)
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
val dataChannelCount = 5
val mockedRandomIdDataChannels: MutableList<DataChannel> = ArrayList()
val dataChannelObservers: MutableList<DataChannel.Observer?> = ArrayList()
for (j in 0..<dataChannelCount) {
mockedRandomIdDataChannels.add(Mockito.mock(DataChannel::class.java))
// Add data channels with duplicated labels (from the second data channel and onwards) to test that
// they are correctly disposed also in that case (which should not happen anyway, but just in case).
Mockito.`when`(mockedRandomIdDataChannels[j].label())
.thenAnswer(ReturnValueOrThrowIfDisposed("random-id-" + ((j + 1) / 2)))
Mockito.`when`(mockedRandomIdDataChannels[j].state())
.thenAnswer(ReturnValueOrThrowIfDisposed(DataChannel.State.OPEN))
// Store a reference to the registered observer, if any, to be called after the registration. The call
// is done outside the mock to better simulate the normal behaviour, as it would not be called during
// the registration itself.
dataChannelObservers.add(null)
doAnswer { invocation ->
if (Mockito.mockingDetails(invocation.mock).invocations.find {
it!!.method.name === "dispose"
} !== null
) {
throw IllegalStateException("DataChannel has been disposed")
}
dataChannelObservers[j] = invocation.getArgument(0, DataChannel.Observer::class.java)
null
}.`when`(mockedRandomIdDataChannels[j]).registerObserver(any())
}
val onDataChannelThread = thread {
// Add again "status" data channel to test that it is correctly disposed also in that case (which
// should not happen anyway even if it was added by the remote peer, but just in case)
peerConnectionObserverArgumentCaptor.value.onDataChannel(mockedStatusDataChannel)
for (j in 0..<dataChannelCount) {
peerConnectionObserverArgumentCaptor.value.onDataChannel(mockedRandomIdDataChannels[j])
// Call "onStateChange" on the registered observer to simulate that the data channel was opened.
dataChannelObservers[j]?.onStateChange()
}
}
// Exceptions thrown in threads are not propagated to the main thread, so it needs to be explicitly done
// (for example, for ConcurrentModificationExceptions when iterating over the data channels).
var exceptionRemovePeerConnection: Exception? = null
val removePeerConnectionThread = thread {
try {
peerConnectionWrapper!!.removePeerConnection()
} catch (e: Exception) {
exceptionRemovePeerConnection = e
}
}
onDataChannelThread.join()
removePeerConnectionThread.join()
if (exceptionRemovePeerConnection !== null) {
throw exceptionRemovePeerConnection!!
}
Mockito.verify(mockedStatusDataChannel).dispose()
for (j in 0..<dataChannelCount) {
Mockito.verify(mockedRandomIdDataChannels[j]).dispose()
}
}
}
@Test
fun testRemovePeerConnectionWhileSendingWithDifferentThreads() {
// A brute force approach is used to test race conditions between different threads just repeating the test
// several times. Due to this the test passing could be a false positive, as it could have been a matter of
// luck, but even if the test may wrongly pass sometimes it is better than nothing (although, in general, with
// that number of reruns, it fails when it should).
for (i in 1..1000) {
val peerConnectionObserverArgumentCaptor: ArgumentCaptor<PeerConnection.Observer> =
ArgumentCaptor.forClass(PeerConnection.Observer::class.java)
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
peerConnectionObserverArgumentCaptor.capture()
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenAnswer(ReturnValueOrThrowIfDisposed("status"))
Mockito.`when`(mockedStatusDataChannel.state())
.thenAnswer(ReturnValueOrThrowIfDisposed(DataChannel.State.OPEN))
Mockito.`when`(mockedStatusDataChannel.send(any())).thenAnswer(ReturnValueOrThrowIfDisposed(true))
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
val dataChannelMessageCount = 5
// Exceptions thrown in threads are not propagated to the main thread, so it needs to be explicitly done
// (for example, for IllegalStateExceptions when using a disposed data channel).
var exceptionSend: Exception? = null
val sendThread = thread {
try {
for (j in 0..<dataChannelMessageCount) {
peerConnectionWrapper!!.send(DataChannelMessage("the-message-type-$j"))
}
} catch (e: Exception) {
exceptionSend = e
}
}
val removePeerConnectionThread = thread {
peerConnectionWrapper!!.removePeerConnection()
}
sendThread.join()
removePeerConnectionThread.join()
if (exceptionSend !== null) {
throw exceptionSend!!
}
Mockito.verify(mockedStatusDataChannel).registerObserver(any())
Mockito.verify(mockedStatusDataChannel).dispose()
Mockito.verify(mockedStatusDataChannel, atLeast(0)).label()
Mockito.verify(mockedStatusDataChannel, atLeast(0)).state()
Mockito.verify(mockedStatusDataChannel, atLeast(0)).send(any())
Mockito.verifyNoMoreInteractions(mockedStatusDataChannel)
}
}
@Test
fun testRemovePeerConnectionWhileReceivingWithDifferentThreads() {
// A brute force approach is used to test race conditions between different threads just repeating the test
// several times. Due to this the test passing could be a false positive, as it could have been a matter of
// luck, but even if the test may wrongly pass sometimes it is better than nothing (although, in general, with
// that number of reruns, it fails when it should).
for (i in 1..1000) {
val peerConnectionObserverArgumentCaptor: ArgumentCaptor<PeerConnection.Observer> =
ArgumentCaptor.forClass(PeerConnection.Observer::class.java)
Mockito.`when`(
mockedPeerConnectionFactory!!.createPeerConnection(
any(PeerConnection.RTCConfiguration::class.java),
peerConnectionObserverArgumentCaptor.capture()
)
).thenReturn(mockedPeerConnection)
val mockedStatusDataChannel = Mockito.mock(DataChannel::class.java)
Mockito.`when`(mockedStatusDataChannel.label()).thenAnswer(ReturnValueOrThrowIfDisposed("status"))
Mockito.`when`(mockedStatusDataChannel.state()).thenAnswer(
ReturnValueOrThrowIfDisposed(DataChannel.State.OPEN)
)
Mockito.`when`(mockedPeerConnection!!.createDataChannel(eq("status"), any()))
.thenReturn(mockedStatusDataChannel)
val statusDataChannelObserverArgumentCaptor: ArgumentCaptor<DataChannel.Observer> =
ArgumentCaptor.forClass(DataChannel.Observer::class.java)
doNothing().`when`(mockedStatusDataChannel)
.registerObserver(statusDataChannelObserverArgumentCaptor.capture())
peerConnectionWrapper = PeerConnectionWrapper(
mockedPeerConnectionFactory,
ArrayList<PeerConnection.IceServer>(),
MediaConstraints(),
"the-session-id",
"the-local-session-id",
null,
true,
true,
"video",
mockedSignalingMessageReceiver,
mockedSignalingMessageSender
)
val mockedDataChannelMessageListener = Mockito.mock(DataChannelMessageListener::class.java)
peerConnectionWrapper!!.addListener(mockedDataChannelMessageListener)
// Exceptions thrown in threads are not propagated to the main thread, so it needs to be explicitly done
// (for example, for IllegalStateExceptions when using a disposed data channel).
var exceptionOnMessage: Exception? = null
val onMessageThread = thread {
try {
// It is assumed that, even if its data channel was disposed, its buffers can be used while there
// is a reference to them, so no special mock behaviour is added to throw an exception in that case.
statusDataChannelObserverArgumentCaptor.value.onMessage(
dataChannelMessageToBuffer(DataChannelMessage("audioOn"))
)
statusDataChannelObserverArgumentCaptor.value.onMessage(
dataChannelMessageToBuffer(DataChannelMessage("audioOff"))
)
} catch (e: Exception) {
exceptionOnMessage = e
}
}
val removePeerConnectionThread = thread {
peerConnectionWrapper!!.removePeerConnection()
}
onMessageThread.join()
removePeerConnectionThread.join()
if (exceptionOnMessage !== null) {
throw exceptionOnMessage!!
}
Mockito.verify(mockedStatusDataChannel).registerObserver(any())
Mockito.verify(mockedStatusDataChannel).dispose()
Mockito.verify(mockedStatusDataChannel, atLeast(0)).label()
Mockito.verify(mockedStatusDataChannel, atLeast(0)).state()
Mockito.verifyNoMoreInteractions(mockedStatusDataChannel)
Mockito.verify(mockedDataChannelMessageListener, atMostOnce()).onAudioOn()
Mockito.verify(mockedDataChannelMessageListener, atMostOnce()).onAudioOff()
Mockito.verifyNoMoreInteractions(mockedDataChannelMessageListener)
}
}
}