Initial import of f7cloud-mobile native Android app.

Kotlin/Compose client for F7cloud (mail, files, talk, calendar, contacts, tasks, support).
Current version: 0.5.113 (build 121).
This commit is contained in:
F7cloud Mobile
2026-07-07 12:05:18 +03:00
commit fd17df80a8
1789 changed files with 246889 additions and 0 deletions
@@ -0,0 +1,56 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package android.util
/**
* Dummy implementation of android.util.Log to be used in unit tests.
*
*
* The Android Gradle plugin provides a library with the APIs of the Android framework that throws an exception if any
* of them are called. This class is loaded before that library and therefore becomes the implementation used during the
* tests, simply printing the messages to the system console.
*/
object Log {
@JvmStatic
fun d(tag: String, msg: String): Int {
println("DEBUG: $tag: $msg")
return 1
}
@JvmStatic
fun e(tag: String, msg: String): Int {
println("ERROR: $tag: $msg")
return 1
}
@JvmStatic
fun i(tag: String, msg: String): Int {
println("INFO: $tag: $msg")
return 1
}
@JvmStatic
fun isLoggable(tag: String?, level: Int): Boolean = true
@JvmStatic
fun v(tag: String, msg: String): Int {
println("VERBOSE: $tag: $msg")
return 1
}
@JvmStatic
fun w(tag: String, msg: String): Int {
println("WARN: $tag: $msg")
return 1
}
}
@@ -0,0 +1,163 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.activities
import ru.f7cloud.talk.signaling.SignalingMessageReceiver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
class CallViewModelTest {
private lateinit var viewModel: CallViewModel
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
viewModel = CallViewModel()
}
@Test
fun `addParticipant adds new participant and updates participants list`() =
testScope.runTest {
val sessionId = "session1"
val mockReceiver = mock<SignalingMessageReceiver>()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = sessionId,
signalingMessageReceiver = mockReceiver
)
testDispatcher.scheduler.advanceUntilIdle()
assertTrue(viewModel.doesParticipantExist(sessionId))
assertTrue(viewModel.participants.value.any { it.sessionKey == sessionId })
}
@Test
fun `doesParticipantExist returns true when participant is added`() {
val sessionId = "session2"
val receiver = mock<SignalingMessageReceiver>()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = sessionId,
signalingMessageReceiver = receiver
)
assertTrue(viewModel.doesParticipantExist(sessionId))
}
@Test
fun `doesParticipantExist returns false for unknown participant`() {
assertFalse(viewModel.doesParticipantExist("unknown"))
}
@Test
fun `onShareScreen sets active screen share session`() {
val sessionId = "screen1"
val receiver = mock<SignalingMessageReceiver>()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = sessionId,
signalingMessageReceiver = receiver
)
viewModel.onShareScreen(sessionId)
val activeSession = viewModel.activeScreenShareSession.value
assertEquals(sessionId, activeSession?.sessionKey)
}
@Test
fun `onUnshareScreen clears active session when same session unshares`() {
val sessionId = "screen2"
val receiver = mock<SignalingMessageReceiver>()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = sessionId,
signalingMessageReceiver = receiver
)
viewModel.onShareScreen(sessionId)
viewModel.onUnshareScreen(sessionId)
assertNull(viewModel.activeScreenShareSession.value)
}
@Test
fun `removeParticipant removes participant and updates participants list`() =
testScope.runTest {
val sessionId = "session3"
val receiver = mock<SignalingMessageReceiver>()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = sessionId,
signalingMessageReceiver = receiver
)
viewModel.removeParticipant(sessionId)
assertFalse(viewModel.doesParticipantExist(sessionId))
assertTrue(viewModel.participants.value.isEmpty())
}
@Test
fun `setActiveScreenShareSession sets proper participant`() {
val sessionId = "screen3"
val receiver = mock<SignalingMessageReceiver>()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = sessionId,
signalingMessageReceiver = receiver
)
viewModel.setActiveScreenShareSession(sessionId)
val active = viewModel.activeScreenShareSession.value
assertNotNull(active)
assertEquals(sessionId, active?.sessionKey)
}
@Test
fun `onCleared destroys all participant handlers`() =
testScope.runTest {
val sessionId = "session1"
val mockReceiver = mock<SignalingMessageReceiver>()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = sessionId,
signalingMessageReceiver = mockReceiver
)
testDispatcher.scheduler.advanceUntilIdle()
assertEquals(1, viewModel.participants.value.size)
viewModel.onCleared()
assertEquals(0, viewModel.participants.value.size)
}
}
@@ -0,0 +1,77 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.activities
import ru.f7cloud.talk.signaling.SignalingMessageReceiver
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
class ParticipantHandlerTest {
private val testDispatcher = StandardTestDispatcher()
private lateinit var signalingMessageReceiver: SignalingMessageReceiver
private lateinit var onParticipantShareScreen: (String?) -> Unit
private lateinit var onParticipantUnshareScreen: (String?) -> Unit
private lateinit var handler: ParticipantHandler
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
signalingMessageReceiver = mock {}
onParticipantShareScreen = mock {}
onParticipantUnshareScreen = mock {}
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `Initial state is correct`() =
runTest {
// Given
val sessionId = "session-123"
// When
handler = ParticipantHandler(
sessionId = sessionId,
baseUrl = "",
roomToken = "",
signalingMessageReceiver = signalingMessageReceiver,
onParticipantShareScreen = onParticipantShareScreen,
onParticipantUnshareScreen = onParticipantUnshareScreen
)
// Then
val expectedState = ParticipantUiState(
sessionKey = sessionId,
baseUrl = "",
roomToken = "",
nick = "Guest",
isConnected = true,
isAudioEnabled = false,
isStreamEnabled = false,
isScreenStreamEnabled = false,
raisedHand = false,
isInternal = false
)
assertEquals(expectedState, handler.uiState.value)
}
}
@@ -0,0 +1,654 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.call;
import ru.f7cloud.talk.models.json.participants.Participant;
import ru.f7cloud.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 ru.f7cloud.talk.models.json.participants.Participant.InCallFlags.DISCONNECTED;
import static ru.f7cloud.talk.models.json.participants.Participant.InCallFlags.IN_CALL;
import static ru.f7cloud.talk.models.json.participants.Participant.InCallFlags.WITH_AUDIO;
import static ru.f7cloud.talk.models.json.participants.Participant.InCallFlags.WITH_VIDEO;
import static ru.f7cloud.talk.models.json.participants.Participant.ParticipantType.GUEST;
import static ru.f7cloud.talk.models.json.participants.Participant.ParticipantType.GUEST_MODERATOR;
import static ru.f7cloud.talk.models.json.participants.Participant.ParticipantType.MODERATOR;
import static ru.f7cloud.talk.models.json.participants.Participant.ParticipantType.OWNER;
import static ru.f7cloud.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);
}
}
@@ -0,0 +1,528 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.call;
import ru.f7cloud.talk.models.json.participants.Participant;
import ru.f7cloud.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 ru.f7cloud.talk.models.json.participants.Participant.InCallFlags.DISCONNECTED;
import static ru.f7cloud.talk.models.json.participants.Participant.InCallFlags.IN_CALL;
import static ru.f7cloud.talk.models.json.participants.Participant.InCallFlags.WITH_AUDIO;
import static ru.f7cloud.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));
}
}
@@ -0,0 +1,48 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.call;
import ru.f7cloud.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);
}
}
@@ -0,0 +1,168 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.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()
}
}
@@ -0,0 +1,682 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.call
import ru.f7cloud.talk.activities.ParticipantUiState
import ru.f7cloud.talk.models.json.signaling.DataChannelMessage
import ru.f7cloud.talk.models.json.signaling.NCMessagePayload
import ru.f7cloud.talk.models.json.signaling.NCSignalingMessage
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.TestScheduler
import org.junit.Before
import org.junit.Ignore
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 participantUiState = createTestParticipantUiState()
localStateBroadcasterMcu!!.handleCallParticipantAdded(participantUiState)
// 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 participantUiState = createTestParticipantUiState()
localStateBroadcasterMcu!!.handleCallParticipantAdded(participantUiState)
// 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)
}
// The test is ignored for now. Somehow it fails with the following error but Daniel C.S. and me were not able to
// understand why. This needs to be investigated!
//
// org.mockito.exceptions.verification.TooManyActualInvocations:
// messageSender.send(
// NCSignalingMessage(from=null, to=null, type=unmute, payload=NCMessagePayload(type=null, sdp=null, nick=null,
// iceCandidate=null, name=audio, state=null, timestamp=null, reaction=null),
// roomType=video, sid=null, prefix=null),
// "theSessionId"
// );
// Wanted 4 times:
// -> at ru.f7cloud.talk.call.MessageSender.send(MessageSender.java:63)
// But was 5 times:
// -> at ru.f7cloud.talk.call.LocalStateBroadcasterMcu.sendState(LocalStateBroadcasterMcu.java:115)
// -> at ru.f7cloud.talk.call.LocalStateBroadcasterMcu.sendState(LocalStateBroadcasterMcu.java:115)
// -> at ru.f7cloud.talk.call.LocalStateBroadcasterMcu.sendState(LocalStateBroadcasterMcu.java:115)
// -> at ru.f7cloud.talk.call.LocalStateBroadcasterMcu.sendState(LocalStateBroadcasterMcu.java:115)
// -> at ru.f7cloud.talk.call.LocalStateBroadcasterMcu.sendState(LocalStateBroadcasterMcu.java:115)
//
//
// at app//ru.f7cloud.talk.call.MessageSender.send(MessageSender.java:63)
// at app//ru.f7cloud.talk.call.LocalStateBroadcasterMcuTest.
// testStateSentWithExponentialBackoffWhenAnotherParticipantAdded(LocalStateBroadcasterMcuTest.kt:370)
@Ignore
@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 participantUiState = createTestParticipantUiState()
localStateBroadcasterMcu!!.handleCallParticipantAdded(participantUiState)
// 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 participantUiState2 = createTestParticipantUiState()
localStateBroadcasterMcu!!.handleCallParticipantAdded(participantUiState2)
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 participantUiState = createTestParticipantUiState()
localStateBroadcasterMcu!!.handleCallParticipantAdded(participantUiState)
// 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(participantUiState.sessionKey)
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 participantUiState = createTestParticipantUiState()
val participantUiState2 = createTestParticipantUiState("theSessionId2")
localStateBroadcasterMcu!!.handleCallParticipantAdded(participantUiState)
localStateBroadcasterMcu!!.handleCallParticipantAdded(participantUiState2)
// 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)
}
private fun createTestParticipantUiState(sessionId: String = "theSessionId"): ParticipantUiState =
ParticipantUiState(
sessionKey = sessionId,
nick = "Guest",
isConnected = false,
isAudioEnabled = false,
isStreamEnabled = false,
isScreenStreamEnabled = false,
raisedHand = false,
isInternal = false,
baseUrl = "",
roomToken = ""
)
}
@@ -0,0 +1,177 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.call
import ru.f7cloud.talk.activities.ParticipantUiState
import ru.f7cloud.talk.models.json.signaling.DataChannelMessage
import ru.f7cloud.talk.models.json.signaling.NCMessagePayload
import ru.f7cloud.talk.models.json.signaling.NCSignalingMessage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito
@OptIn(ExperimentalCoroutinesApi::class)
class LocalStateBroadcasterNoMcuTest {
private lateinit var localCallParticipantModel: MutableLocalCallParticipantModel
private lateinit var mockedMessageSenderNoMcu: MessageSenderNoMcu
private lateinit var localStateBroadcasterNoMcu: LocalStateBroadcasterNoMcu
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
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 testStateSentWhenParticipantConnects() =
testScope.runTest {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu,
testScope
)
val initialState = createTestParticipantUiState(
sessionId = "theSessionId",
isConnected = false
)
localStateBroadcasterNoMcu.handleCallParticipantAdded(initialState)
advanceUntilIdle()
// Verify nothing is sent because isConnected is false
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
// State 2: The same participant's state is updated to connected
val connectedState = initialState.copy(isConnected = true)
localStateBroadcasterNoMcu.handleCallParticipantAdded(connectedState)
advanceUntilIdle() // Allow the broadcaster to react
verifyStateSent("theSessionId")
Mockito.verifyNoMoreInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateNotSentAfterParticipantIsRemoved() =
testScope.runTest {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu,
testScope
)
val initialState = createTestParticipantUiState(
sessionId = "theSessionId",
isConnected = false
)
localStateBroadcasterNoMcu.handleCallParticipantAdded(initialState)
localStateBroadcasterNoMcu.handleCallParticipantRemoved("theSessionId")
advanceUntilIdle()
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
}
@Test
fun testStateNotSentAfterDestroyed() =
testScope.runTest {
localStateBroadcasterNoMcu = LocalStateBroadcasterNoMcu(
localCallParticipantModel,
mockedMessageSenderNoMcu,
testScope
)
val initialState = createTestParticipantUiState(
sessionId = "theSessionId",
isConnected = false
)
localStateBroadcasterNoMcu.handleCallParticipantAdded(initialState)
localStateBroadcasterNoMcu.destroy()
advanceUntilIdle()
Mockito.verifyNoInteractions(mockedMessageSenderNoMcu)
}
private fun verifyStateSent(sessionId: String) {
val expectedAudioOn = DataChannelMessage("audioOn")
val expectedSpeaking = DataChannelMessage("speaking")
val expectedVideoOn = DataChannelMessage("videoOn")
val expectedUnmuteAudio = getExpectedUnmuteAudio()
val expectedUnmuteVideo = getExpectedUnmuteVideo()
Mockito.verify(mockedMessageSenderNoMcu).send(expectedAudioOn, sessionId)
Mockito.verify(mockedMessageSenderNoMcu).send(expectedSpeaking, sessionId)
Mockito.verify(mockedMessageSenderNoMcu).send(expectedVideoOn, sessionId)
Mockito.verify(mockedMessageSenderNoMcu).send(expectedUnmuteAudio, sessionId)
Mockito.verify(mockedMessageSenderNoMcu).send(expectedUnmuteVideo, sessionId)
}
private fun createTestParticipantUiState(
sessionId: String = "theSessionId",
isConnected: Boolean = false
): ParticipantUiState =
ParticipantUiState(
sessionKey = sessionId,
nick = "Guest",
isConnected = isConnected,
isAudioEnabled = false,
isStreamEnabled = false,
isScreenStreamEnabled = false,
raisedHand = false,
isInternal = false,
baseUrl = "",
roomToken = ""
)
}
@@ -0,0 +1,325 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.call
import ru.f7cloud.talk.activities.ParticipantUiState
import ru.f7cloud.talk.models.json.signaling.DataChannelMessage
import ru.f7cloud.talk.models.json.signaling.NCMessagePayload
import ru.f7cloud.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?
) : ru.f7cloud.talk.call.LocalStateBroadcaster(localCallParticipantModel, messageSender) {
override fun handleCallParticipantAdded(uiState: ParticipantUiState) {
// Not used in base class tests
}
override fun handleCallParticipantRemoved(sessionId: String) {
// 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)
}
}
@@ -0,0 +1,118 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.call
import ru.f7cloud.talk.models.json.signaling.DataChannelMessage
import ru.f7cloud.talk.signaling.SignalingMessageSender
import ru.f7cloud.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, Set<String>>()
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)
}
}
@@ -0,0 +1,101 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.call
import ru.f7cloud.talk.models.json.signaling.DataChannelMessage
import ru.f7cloud.talk.signaling.SignalingMessageSender
import ru.f7cloud.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, Set<String>>()
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)
}
}
@@ -0,0 +1,188 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.call
import ru.f7cloud.talk.activities.CallViewModel
import ru.f7cloud.talk.models.json.signaling.DataChannelMessage
import ru.f7cloud.talk.models.json.signaling.NCSignalingMessage
import ru.f7cloud.talk.signaling.SignalingMessageReceiver
import ru.f7cloud.talk.signaling.SignalingMessageSender
import ru.f7cloud.talk.webrtc.PeerConnectionWrapper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
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
import org.mockito.kotlin.mock
@OptIn(ExperimentalCoroutinesApi::class)
class MessageSenderTest {
private class MessageSender(
signalingMessageSender: SignalingMessageSender?,
callParticipantSessionIds: Set<String>?,
peerConnectionWrappers: List<PeerConnectionWrapper>?
) : ru.f7cloud.talk.call.MessageSender(
signalingMessageSender,
callParticipantSessionIds,
peerConnectionWrappers
) {
override fun sendToAll(dataChannelMessage: DataChannelMessage?) {
// Not used in base class tests
}
}
private var signalingMessageSender: SignalingMessageSender? = null
private lateinit var viewModel: CallViewModel
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
private var messageSender: MessageSender? = null
val mockReceiver = mock<SignalingMessageReceiver>()
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
viewModel = CallViewModel()
signalingMessageSender = Mockito.mock(SignalingMessageSender::class.java)
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = "theSessionId1",
signalingMessageReceiver = mockReceiver
)
testDispatcher.scheduler.advanceUntilIdle()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = "theSessionId2",
signalingMessageReceiver = mockReceiver
)
testDispatcher.scheduler.advanceUntilIdle()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = "theSessionId3",
signalingMessageReceiver = mockReceiver
)
testDispatcher.scheduler.advanceUntilIdle()
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = "theSessionId4",
signalingMessageReceiver = mockReceiver
)
testDispatcher.scheduler.advanceUntilIdle()
val peerConnectionWrappers = ArrayList<PeerConnectionWrapper>()
val sessionKeys = viewModel.participants.value
.mapNotNull { it.sessionKey }
.toSet()
messageSender = MessageSender(signalingMessageSender, sessionKeys, 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() =
testScope.runTest {
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() =
testScope.runTest {
viewModel.addParticipant(
baseUrl = "",
roomToken = "",
sessionId = "theSessionId5",
signalingMessageReceiver = mockReceiver
)
testDispatcher.scheduler.advanceUntilIdle()
viewModel.removeParticipant("theSessionId2")
testDispatcher.scheduler.advanceUntilIdle()
viewModel.removeParticipant("theSessionId3")
testDispatcher.scheduler.advanceUntilIdle()
val updatedSessionKeys = viewModel.participants.value
.mapNotNull { it.sessionKey }
.toSet()
messageSender = MessageSender(signalingMessageSender, updatedSessionKeys, emptyList())
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)
}
}
@@ -0,0 +1,146 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.contacts
import ru.f7cloud.talk.contacts.apiService.FakeItem
import ru.f7cloud.talk.contacts.repository.FakeRepositoryError
import ru.f7cloud.talk.contacts.repository.FakeRepositorySuccess
import ru.f7cloud.talk.data.user.UsersDao
import ru.f7cloud.talk.data.user.UsersRepository
import ru.f7cloud.talk.data.user.UsersRepositoryImpl
import ru.f7cloud.talk.users.UserManager
import ru.f7cloud.talk.utils.database.user.CurrentUserProviderOld
import ru.f7cloud.talk.utils.database.user.CurrentUserProviderOldImpl
import ru.f7cloud.talk.utils.preview.DummyUserDaoImpl
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()
val usersDao: UsersDao
get() = DummyUserDaoImpl()
val userRepository: UsersRepository
get() = UsersRepositoryImpl(usersDao)
val userManager: UserManager
get() = UserManager(userRepository)
val userProvider: CurrentUserProviderOld
get() = CurrentUserProviderOldImpl(userManager)
@Before
fun setup() {
Dispatchers.setMain(dispatcher)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Before
fun setUp() {
viewModel = ContactsViewModel(repository, userProvider)
}
@Test
fun `fetch contacts`() =
runTest {
viewModel = ContactsViewModel(repository, userProvider)
viewModel.getContactsFromSearchParams()
assert(viewModel.contactsViewState.value is ContactsViewModel.ContactsUiState.Success)
val successState = viewModel.contactsViewState.value as ContactsViewModel.ContactsUiState.Success
assert(successState.contacts == FakeItem.contacts)
}
@Test
fun `test error contacts state`() =
runTest {
viewModel = ContactsViewModel(FakeRepositoryError(), userProvider)
viewModel.getContactsFromSearchParams()
assert(viewModel.contactsViewState.value is ContactsViewModel.ContactsUiState.Error)
val errorState = viewModel.contactsViewState.value as ContactsViewModel.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 ContactsViewModel.RoomUiState.None)
}
@Test
fun `test success room state`() =
runTest {
viewModel.createRoom("1", "users", "s@gmail.com", null)
assert(viewModel.roomViewState.value is ContactsViewModel.RoomUiState.Success)
val successState = viewModel.roomViewState.value as ContactsViewModel.RoomUiState.Success
assert(successState.conversation == FakeItem.roomOverall.ocs!!.data)
}
@Test
fun `test failure room state`() =
runTest {
viewModel = ContactsViewModel(FakeRepositoryError(), userProvider)
viewModel.createRoom("1", "users", "s@gmail.com", null)
assert(viewModel.roomViewState.value is ContactsViewModel.RoomUiState.Error)
val errorState = viewModel.roomViewState.value as ContactsViewModel.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)
}
}
@@ -0,0 +1,58 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.contacts.apiService
import ru.f7cloud.talk.models.json.autocomplete.AutocompleteOCS
import ru.f7cloud.talk.models.json.autocomplete.AutocompleteOverall
import ru.f7cloud.talk.models.json.autocomplete.AutocompleteUser
import ru.f7cloud.talk.models.json.conversations.Conversation
import ru.f7cloud.talk.models.json.conversations.RoomOCS
import ru.f7cloud.talk.models.json.conversations.RoomOverall
import ru.f7cloud.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)
)
)
}
@@ -0,0 +1,39 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kota@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.contacts.repository
import ru.f7cloud.talk.contacts.ContactsRepository
import ru.f7cloud.talk.data.user.model.User
import ru.f7cloud.talk.models.json.autocomplete.AutocompleteOverall
import ru.f7cloud.talk.models.json.autocomplete.AutocompleteUser
import ru.f7cloud.talk.models.json.conversations.RoomOverall
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class FakeRepositoryError : ContactsRepository {
@Suppress("Detekt.TooGenericExceptionThrown")
override suspend fun getContacts(user: User, searchQuery: String?, shareTypes: List<String>): AutocompleteOverall =
throw Exception("unable to fetch contacts")
@Suppress("Detekt.TooGenericExceptionThrown")
override suspend fun createRoom(
user: User,
roomType: String,
sourceType: String?,
userId: String,
conversationName: String?
): RoomOverall = throw Exception("unable to create room")
override fun getImageUri(user: User, avatarId: String, requestBigSize: Boolean) =
"https://mydoman.com/index.php/avatar/$avatarId/512"
override fun getContactsFlow(user: User, searchQuery: String?): Flow<List<AutocompleteUser>> =
flow {
// unused atm
}
}
@@ -0,0 +1,36 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Sowjanya Kota <sowjanya.kch@email.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.contacts.repository
import ru.f7cloud.talk.contacts.ContactsRepository
import ru.f7cloud.talk.contacts.apiService.FakeItem
import ru.f7cloud.talk.data.user.model.User
import ru.f7cloud.talk.models.json.autocomplete.AutocompleteUser
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
class FakeRepositorySuccess : ContactsRepository {
override suspend fun getContacts(user: User, searchQuery: String?, shareTypes: List<String>) =
FakeItem.contactsOverall
override suspend fun createRoom(
user: User,
roomType: String,
sourceType: String?,
userId: String,
conversationName: String?
) = FakeItem.roomOverall
override fun getImageUri(user: User, avatarId: String, requestBigSize: Boolean) =
"https://mydomain.com/index.php/avatar/$avatarId/512"
override fun getContactsFlow(user: User, searchQuery: String?): Flow<List<AutocompleteUser>> =
flow {
// unused atm
}
}
@@ -0,0 +1,25 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.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)
}
}
@@ -0,0 +1,174 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.json
import com.bluelinelabs.logansquare.LoganSquare
import ru.f7cloud.talk.data.database.mappers.asEntity
import ru.f7cloud.talk.data.database.mappers.asModel
import ru.f7cloud.talk.data.database.model.ConversationEntity
import ru.f7cloud.talk.models.json.conversations.ConversationEnums
import ru.f7cloud.talk.models.json.conversations.RoomOverall
import ru.f7cloud.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)
}
}
@@ -0,0 +1,596 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.login.data
import android.os.Bundle
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.lifecycle.LiveData
import androidx.work.WorkInfo
import ru.f7cloud.talk.account.data.LoginRepository
import ru.f7cloud.talk.account.data.io.LocalLoginDataSource
import ru.f7cloud.talk.account.data.model.LoginCompletion
import ru.f7cloud.talk.account.data.model.LoginResponse
import ru.f7cloud.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 = "f7://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 = "f7://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 = "f7://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 = "f7://login/"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNull(result)
}
@Test
fun `startLoginFlowFromQR sets reAuth flag correctly`() {
// Arrange
val qrData = "f7://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 = "f7://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 = "f7://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 = "f7://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 = "f7://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 = "f7://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 = "f7://login/user:testuser&server:"
// Act
val result = repo.startLoginFlowFromQR(qrData)
// Assert
assertNull(result)
}
}
@@ -0,0 +1,167 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.login.data.network
import ru.f7cloud.talk.account.data.model.LoginResponse
import ru.f7cloud.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)
}
}
@@ -0,0 +1,165 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 F7cloud
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.messagesearch
import ru.f7cloud.talk.data.user.UsersDao
import ru.f7cloud.talk.data.user.UsersRepository
import ru.f7cloud.talk.data.user.UsersRepositoryImpl
import ru.f7cloud.talk.models.domain.SearchMessageEntry
import ru.f7cloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import ru.f7cloud.talk.test.fakes.FakeUnifiedSearchRepository
import ru.f7cloud.talk.users.UserManager
import ru.f7cloud.talk.utils.database.user.CurrentUserProviderOld
import ru.f7cloud.talk.utils.database.user.CurrentUserProviderOldImpl
import ru.f7cloud.talk.utils.preview.DummyUserDaoImpl
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()
val usersDao: UsersDao
get() = DummyUserDaoImpl()
val userRepository: UsersRepository
get() = UsersRepositoryImpl(usersDao)
val userManager: UserManager
get() = UserManager(userRepository)
val userProvider: CurrentUserProviderOld
get() = CurrentUserProviderOldImpl(userManager)
@Suppress("LongParameterList")
private fun createMessageEntry(
searchTerm: String = "foo",
thumbnailURL: String = "foo",
title: String = "foo",
messageExcerpt: String = "foo",
conversationToken: String = "foo",
messageId: String? = "foo",
threadId: String? = "foo"
) = SearchMessageEntry(searchTerm, thumbnailURL, title, messageExcerpt, conversationToken, threadId, messageId)
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
}
@Test
fun emptySearch() {
repository.response = UnifiedSearchRepository.UnifiedSearchResults(0, false, emptyList())
val sut = MessageSearchHelper(
repository,
currentUser = userProvider.currentUser.blockingGet()
)
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,
currentUser = userProvider.currentUser.blockingGet()
)
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,
currentUser = userProvider.currentUser.blockingGet()
)
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,
currentUser = userProvider.currentUser.blockingGet()
)
repeat(5) {
val observable = sut.startMessageSearch("foo")
val expected = MessageSearchHelper.MessageSearchResults(entries, false)
testCall(observable, expected)
}
}
@Test
fun loadMore_noPreviousResults() {
val sut = MessageSearchHelper(
repository,
currentUser = userProvider.currentUser.blockingGet()
)
Assert.assertEquals(null, sut.loadMore())
}
@Test
fun loadMore_previousResults_sameSearch() {
val sut = MessageSearchHelper(
repository,
currentUser = userProvider.currentUser.blockingGet()
)
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()
}
}
@@ -0,0 +1,262 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.signaling;
import ru.f7cloud.talk.models.json.signaling.NCMessagePayload;
import ru.f7cloud.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();
}
}
@@ -0,0 +1,180 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.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");
}
}
@@ -0,0 +1,218 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.signaling;
import ru.f7cloud.talk.models.json.signaling.NCMessagePayload;
import ru.f7cloud.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");
}
}
@@ -0,0 +1,461 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.signaling;
import ru.f7cloud.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);
}
}
@@ -0,0 +1,122 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.signaling;
import ru.f7cloud.talk.models.json.signaling.NCMessagePayload;
import ru.f7cloud.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);
}
}
@@ -0,0 +1,353 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.signaling;
import ru.f7cloud.talk.models.json.signaling.NCIceCandidate;
import ru.f7cloud.talk.models.json.signaling.NCMessagePayload;
import ru.f7cloud.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();
}
}
@@ -0,0 +1,22 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 F7cloud
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.test.fakes
import ru.f7cloud.talk.models.domain.StartCallRecordingModel
import ru.f7cloud.talk.models.domain.StopCallRecordingModel
import ru.f7cloud.talk.repositories.callrecording.CallRecordingRepository
import io.reactivex.Observable
class FakeCallRecordingRepository : CallRecordingRepository {
override fun startRecording(credentials: String?, url: String, roomToken: String) =
Observable.just(StartCallRecordingModel(true))
override fun stopRecording(credentials: String?, url: String, roomToken: String) =
Observable.just(StopCallRecordingModel(true))
}
@@ -0,0 +1,41 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2022 F7cloud
* SPDX-FileCopyrightText: 2022 Álvaro Brey <alvaro@alvarobrey.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.test.fakes
import ru.f7cloud.talk.models.domain.SearchMessageEntry
import ru.f7cloud.talk.repositories.unifiedsearch.UnifiedSearchRepository
import io.reactivex.Observable
class FakeUnifiedSearchRepository : UnifiedSearchRepository {
lateinit var response: UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry>
var lastRequestedCursor = -1
override fun searchMessages(
credentials: String?,
url: String,
searchTerm: String,
cursor: Int,
limit: Int
): Observable<UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry>> {
lastRequestedCursor = cursor
return Observable.just(response)
}
override fun searchInRoom(
credentials: String?,
url: String,
roomToken: String,
searchTerm: String,
cursor: Int,
limit: Int
): Observable<UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry>> {
lastRequestedCursor = cursor
return Observable.just(response)
}
}
@@ -0,0 +1,90 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Samanwith KSN <samanwith21@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.utils
import ru.f7cloud.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)
}
}
@@ -0,0 +1,60 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2017-2019 Mario Danic <mario@lovelyhq.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.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));
}
}
@@ -0,0 +1,97 @@
/*
* F7cloud 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 ru.f7cloud.talk.utils
import ru.f7cloud.talk.data.user.model.User
import ru.f7cloud.talk.models.domain.ConversationModel
import ru.f7cloud.talk.models.json.capabilities.SpreedCapability
import ru.f7cloud.talk.models.json.conversations.Conversation
import ru.f7cloud.talk.models.json.conversations.ConversationEnums
import ru.f7cloud.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 = ""
)
}
@@ -0,0 +1,42 @@
/*
* F7cloud 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 ru.f7cloud.talk.utils
import ru.f7cloud.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)
}
}
@@ -0,0 +1,40 @@
/*
* F7cloud 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 ru.f7cloud.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()
}
}
}
}
@@ -0,0 +1,133 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.viewmodels
import ru.f7cloud.talk.data.user.UsersDao
import ru.f7cloud.talk.data.user.UsersRepository
import ru.f7cloud.talk.data.user.UsersRepositoryImpl
import ru.f7cloud.talk.test.fakes.FakeCallRecordingRepository
import ru.f7cloud.talk.users.UserManager
import ru.f7cloud.talk.utils.database.user.CurrentUserProviderOld
import ru.f7cloud.talk.utils.database.user.CurrentUserProviderOldImpl
import ru.f7cloud.talk.utils.preview.DummyUserDaoImpl
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()
val usersDao: UsersDao
get() = DummyUserDaoImpl()
val userRepository: UsersRepository
get() = UsersRepositoryImpl(usersDao)
val userManager: UserManager
get() = UserManager(userRepository)
val userProvider: CurrentUserProviderOld
get() = CurrentUserProviderOldImpl(userManager)
@Before
fun setUp() {
MockitoAnnotations.openMocks(this)
}
@Test
fun testCallRecordingViewModel_clickStartRecord() {
val viewModel = CallRecordingViewModel(repository, userProvider)
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, userProvider)
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, userProvider)
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, userProvider)
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, userProvider)
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)
}
}
@@ -0,0 +1,75 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Samanwith KSN <samanwith21@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.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)
}
}
@@ -0,0 +1,27 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Samanwith KSN <samanwith21@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.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)
}
}
@@ -0,0 +1,68 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2023 Samanwith KSN <samanwith21@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.webrtc
import ru.f7cloud.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)
}
}
@@ -0,0 +1,760 @@
/*
* F7cloud Talk - Android Client
*
* SPDX-FileCopyrightText: 2024 Daniel Calviño Sánchez <danxuliu@gmail.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
package ru.f7cloud.talk.webrtc
import com.bluelinelabs.logansquare.LoganSquare
import ru.f7cloud.talk.models.json.signaling.DataChannelMessage
import ru.f7cloud.talk.signaling.SignalingMessageReceiver
import ru.f7cloud.talk.signaling.SignalingMessageSender
import ru.f7cloud.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)
}
}
}