<NME> BackendNotifierTest.php
<BEF> <?php
/**
 *
 * @copyright Copyright (c) 2018, Joachim Bauch (bauch@struktur.de)
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

namespace OCA\Talk\Tests\php\Signaling;

use OCA\Talk\AppInfo\Application;
use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Config;
use OCA\Talk\Events\SignalingRoomPropertiesEvent;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\AttendeeMapper;
use OCA\Talk\Model\SessionMapper;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RoomService;
use OCA\Talk\Signaling\BackendNotifier;
use OCA\Talk\TalkSession;
use OCA\Talk\Webinary;
use OCP\App\IAppManager;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Http\Client\IClientService;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Security\IHasher;
use OCP\Security\ISecureRandom;
use OCP\Share\IManager;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;

class CustomBackendNotifier extends BackendNotifier {
	private array $requests = [];

	public function getRequests(): array {
		return $this->requests;
	}

	public function clearRequests() {
		$this->requests = [];
	}

	protected function doRequest(string $url, array $params, int $retries = 3): void {
		$this->requests[] = [
			'url' => $url,
			'params' => $params,
		];
	}
}

/**
 * @group DB
 */
class BackendNotifierTest extends TestCase {
	private ?Config $config = null;
	private ?ISecureRandom $secureRandom = null;
	/** @var ITimeFactory|MockObject */
	private $timeFactory;
	/** @var ParticipantService|MockObject */
	private $participantService;
	/** @var \OCA\Talk\Signaling\Manager|MockObject */
	private $signalingManager;
	/** @var IURLGenerator|MockObject */
	private $urlGenerator;
	private ?\OCA\Talk\Tests\php\Signaling\CustomBackendNotifier $controller = null;

	private ?Manager $manager = null;
	private ?RoomService $roomService = null;

	private ?string $userId = null;
	private ?string $signalingSecret = null;
	private ?string $baseUrl = null;

	protected Application $app;
	protected BackendNotifier $originalBackendNotifier;
	private ?IEventDispatcher $dispatcher = null;
	/** @var IJobList|MockObject */
	private IJobList $jobList;

	public function setUp(): void {
		parent::setUp();

		$this->userId = 'testUser';
		$this->secureRandom = \OC::$server->getSecureRandom();
		$this->timeFactory = $this->createMock(ITimeFactory::class);
		$this->urlGenerator = $this->createMock(IURLGenerator::class);
		$groupManager = $this->createMock(IGroupManager::class);
		$userManager = $this->createMock(IUserManager::class);
		$config = \OC::$server->getConfig();
		$this->signalingSecret = 'the-signaling-secret';
		$this->baseUrl = 'https://localhost/signaling';
		$config->setAppValue('spreed', 'signaling_servers', json_encode([
			'secret' => $this->signalingSecret,
			'servers' => [
				[
					'server' => $this->baseUrl,
				],
			],
		]));

		$this->participantService = \OC::$server->get(ParticipantService::class);
		$this->signalingManager = $this->createMock(\OCA\Talk\Signaling\Manager::class);
		$this->signalingManager->expects($this->any())
			->method('getSignalingServerForConversation')
			->willReturn(['server' => $this->baseUrl]);

		$this->dispatcher = \OC::$server->get(IEventDispatcher::class);
		$this->config = new Config($config, $this->secureRandom, $groupManager, $userManager, $this->urlGenerator, $this->timeFactory, $this->dispatcher);
		$this->recreateBackendNotifier();

		$this->overwriteService(BackendNotifier::class, $this->controller);

		$dbConnection = \OC::$server->getDatabaseConnection();
		$this->manager = new Manager(
			$dbConnection,
			$config,
			$this->config,
			\OC::$server->get(IAppManager::class),
			\OC::$server->get(AttendeeMapper::class),
			\OC::$server->get(SessionMapper::class),
			$this->participantService,
			$this->secureRandom,
			$this->createMock(IUserManager::class),
			$groupManager,
			$this->createMock(CommentsManager::class),
			$this->createMock(TalkSession::class),
			$this->dispatcher,
			$this->timeFactory,
			$this->createMock(IHasher::class),
			$this->createMock(IL10N::class)
		);
		$this->jobList = $this->createMock(IJobList::class);

		$this->roomService = new RoomService(
			$this->manager,
			$this->participantService,
			$dbConnection,
			$this->timeFactory,
			$this->createMock(IManager::class),
			$this->createMock(IHasher::class),
			$this->dispatcher,
			$this->jobList
		);
	}

	public function tearDown(): void {
		$config = \OC::$server->getConfig();
		$config->deleteAppValue('spreed', 'signaling_servers');
		$this->restoreService(BackendNotifier::class);
		parent::tearDown();
	}

	private function recreateBackendNotifier() {
		$this->controller = new CustomBackendNotifier(
			$this->config,
			$this->createMock(LoggerInterface::class),
			$this->createMock(IClientService::class),
			$this->secureRandom,
			$this->signalingManager,
			$this->participantService,
			$this->urlGenerator
		);
	}

	private function calculateBackendChecksum($data, $random) {
		if (empty($random) || strlen($random) < 32) {
			return false;
		}
		return hash_hmac('sha256', $random . $data, $this->signalingSecret);
	}

	private function validateBackendRequest($expectedUrl, $request) {
		$this->assertTrue(isset($request));
		$this->assertEquals($expectedUrl, $request['url']);
		$headers = $request['params']['headers'];
		$this->assertEquals('application/json', $headers['Content-Type']);
		$random = $headers['Spreed-Signaling-Random'];
		$checksum = $headers['Spreed-Signaling-Checksum'];
		$body = $request['params']['body'];
		$this->assertEquals($this->calculateBackendChecksum($body, $random), $checksum);
		return $body;
	}

	private function assertMessageWasSent(Room $room, array $message): void {
		$requests = $this->controller->getRequests();
		$bodies = array_map(function ($request) use ($room) {
			return json_decode($this->validateBackendRequest($this->baseUrl . '/api/v1/room/' . $room->getToken(), $request), true);
		}, $requests);

		$bodies = array_filter($bodies, function (array $body) use ($message) {
			return $body['type'] === $message['type'];
		});

		$bodies = array_map([$this, 'sortParticipantUsers'], $bodies);
		$message = $this->sortParticipantUsers($message);
		$this->assertContainsEquals($message, $bodies, json_encode($bodies, JSON_PRETTY_PRINT));
	}

	private function assertNoMessageOfTypeWasSent(Room $room, string $messageType): void {
		$requests = $this->controller->getRequests();
		$bodies = array_map(function ($request) use ($room) {
			return json_decode($this->validateBackendRequest($this->baseUrl . '/api/v1/room/' . $room->getToken(), $request), true);
		}, $requests);

		$bodies = array_filter($bodies, function (array $body) use ($messageType) {
			return $body['type'] === $messageType;
		});

		$this->assertEmpty($bodies);
	}

	private function sortParticipantUsers(array $message): array {
		if ($message['type'] === 'participants') {
			usort($message['participants']['users'], static function ($a, $b) {
				return
					[$a['userId'] ?? '', $a['participantType'], $a['sessionId'], $a['lastPing']]
					<=>
					[$b['userId'] ?? '', $b['participantType'], $b['sessionId'], $b['lastPing']]
				;
			});
		}
		if ($message['type'] === 'incall') {
			usort($message['incall']['users'], static function ($a, $b) {
				return
					[$a['userId'] ?? '', $a['participantType'], $a['sessionId'], $a['lastPing']]
					<=>
					[$b['userId'] ?? '', $b['participantType'], $b['sessionId'], $b['lastPing']]
				;
			});
		}
		return $message;
	}

	public function testRoomInvite() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->participantService->addUsers($room, [[
			'actorType' => 'users',
			'actorId' => $this->userId,
			'userId' => $this->userId,
			'sessionId' => $userSession,
		]);
		$room->changeInCall($userSession, Room::FLAG_IN_CALL | Room::FLAG_WITH_AUDIO | Room::FLAG_WITH_VIDEO);

		$requests = $this->controller->getRequests();
		$bodies = array_map(function($request) use ($room) {
				],
				'alluserids' => [
					$this->userId,
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
					'participant-list' => 'refresh',
				],
			],
		]);
	}

	public function testRoomDisinvite() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->participantService->addUsers($room, [[
			'actorType' => 'users',
			'actorId' => $this->userId,
		]]);
		$this->controller->clearRequests();

		$this->controller->clearRequests();
		$guestSession = $room->joinRoomGuest('');
		$room->changeInCall($guestSession, Room::FLAG_IN_CALL);

		$requests = $this->controller->getRequests();
		$bodies = array_map(function($request) use ($room) {
		$this->assertMessageWasSent($room, [
			'type' => 'disinvite',
			'disinvite' => [
				'userids' => [
					$this->userId,
				],
				'alluserids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
					'participant-list' => 'refresh',
				],
			],
		]);
	}

	public function testRoomDisinviteOnRemovalOfGuest() {
		$roomService = $this->createMock(RoomService::class);
		$roomService->method('verifyPassword')
			->willReturn(['result' => true, 'url' => '']);

		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$participant = $this->participantService->joinRoomAsNewGuest($roomService, $room, '');
		], $bodies);

		$this->controller->clearRequests();
		$room->changeInCall($userSession, 0);

		$requests = $this->controller->getRequests();
		$bodies = array_map(function($request) use ($room) {
					$participant->getSession()->getSessionId(),
				],
				'alluserids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
					'participant-list' => 'refresh',
				],
			],
		]);
	}

	public function testNoRoomDisinviteOnLeaveOfNormalUser() {
		/** @var IUser|MockObject $testUser */
		$testUser = $this->createMock(IUser::class);
		$testUser->expects($this->any())
			->method('getUID')
			->willReturn($this->userId);

		$roomService = $this->createMock(RoomService::class);
		$roomService->method('verifyPassword')
			->willReturn(['result' => true, 'url' => '']);

		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->participantService->addUsers($room, [[
			'actorType' => 'users',
			'actorId' => $this->userId,
		]]);
		$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');
		$this->controller->clearRequests();
		$this->participantService->leaveRoomAsSession($room, $participant);

		$this->assertNoMessageOfTypeWasSent($room, 'disinvite');
	}

	public function testRoomDisinviteOnLeaveOfNormalUserWithDuplicatedSession() {
		/** @var IUser|MockObject $testUser */
		$testUser = $this->createMock(IUser::class);
		$testUser->expects($this->any())
			->method('getUID')
			->willReturn($this->userId);

		$roomService = $this->createMock(RoomService::class);
		$roomService->method('verifyPassword')
			->willReturn(['result' => true, 'url' => '']);

		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->participantService->addUsers($room, [[
			'actorType' => 'users',
			'actorId' => $this->userId,
		]]);
		$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');
		$this->controller->clearRequests();
		$this->participantService->leaveRoomAsSession($room, $participant, true);

		$this->assertMessageWasSent($room, [
			'type' => 'disinvite',
			'disinvite' => [
				'sessionids' => [
					$participant->getSession()->getSessionId(),
				],
				'alluserids' => [
					$this->userId,
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
					'participant-list' => 'refresh',
				],
			],
		]);
	}

	public function testRoomDisinviteOnLeaveOfSelfJoinedUser() {
		/** @var IUser|MockObject $testUser */
		$testUser = $this->createMock(IUser::class);
		$testUser->expects($this->any())
			->method('getUID')
			->willReturn($this->userId);

		$roomService = $this->createMock(RoomService::class);
		$roomService->method('verifyPassword')
			->willReturn(['result' => true, 'url' => '']);

		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');
		$this->controller->clearRequests();
		$this->participantService->leaveRoomAsSession($room, $participant);

		$this->assertMessageWasSent($room, [
			'type' => 'disinvite',
			'disinvite' => [
				'userids' => [
					$this->userId,
				],
				'alluserids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
					'participant-list' => 'refresh',
				],
			],
		]);
	}

	public function testRoomDisinviteOnLeaveOfGuest() {
		$roomService = $this->createMock(RoomService::class);
		$roomService->method('verifyPassword')
			->willReturn(['result' => true, 'url' => '']);

		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$participant = $this->participantService->joinRoomAsNewGuest($roomService, $room, '');
		$this->controller->clearRequests();
		$this->participantService->leaveRoomAsSession($room, $participant);

		$this->assertMessageWasSent($room, [
			'type' => 'disinvite',
			'disinvite' => [
				'sessionids' => [
					$participant->getSession()->getSessionId(),
				],
				'alluserids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
					'participant-list' => 'refresh',
				],
			],
		]);
	}

	public function testRoomNameChanged() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->roomService->setName($room, 'Test room');

		$this->assertMessageWasSent($room, [
			'type' => 'update',
			'update' => [
				'userids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'description' => '',
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
				],
			],
		]);
	}

	public function testRoomDescriptionChanged() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->roomService->setDescription($room, 'The description');

		$this->assertMessageWasSent($room, [
			'type' => 'update',
			'update' => [
				'userids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'description' => 'The description',
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
				],
			],
		]);
	}

	public function testRoomPasswordChanged() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->roomService->setPassword($room, 'password');

		$this->assertMessageWasSent($room, [
			'type' => 'update',
			'update' => [
				'userids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'description' => '',
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
				],
			],
		]);
	}

	public function testRoomTypeChanged() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->roomService->setType($room, Room::TYPE_GROUP);

		$this->assertMessageWasSent($room, [
			'type' => 'update',
			'update' => [
				'userids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'description' => '',
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
				],
			],
		]);
	}

	public function testRoomReadOnlyChanged() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->roomService->setReadOnly($room, Room::READ_ONLY);

		$this->assertMessageWasSent($room, [
			'type' => 'update',
			'update' => [
				'userids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'description' => '',
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_ONLY,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
				],
			],
		]);
	}

	public function testRoomListableChanged() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->roomService->setListable($room, Room::LISTABLE_ALL);

		$this->assertMessageWasSent($room, [
			'type' => 'update',
			'update' => [
				'userids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_ALL,
					'active-since' => null,
					'sip-enabled' => 0,
					'description' => '',
				],
			],
		]);
	}

	public function testRoomLobbyStateChanged() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->roomService->setLobby($room, Webinary::LOBBY_NON_MODERATORS, null);

		$this->assertMessageWasSent($room, [
			'type' => 'update',
			'update' => [
				'userids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'description' => '',
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NON_MODERATORS,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
				],
			],
		]);
	}

	public function testRoomDelete(): void {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->participantService->addUsers($room, [[
			'actorType' => 'users',
			'actorId' => $this->userId,
		]]);
		$this->roomService->deleteRoom($room);

		$this->assertMessageWasSent($room, [
			'type' => 'delete',
			'delete' => [
				'userids' => [
					$this->userId,
				],
			],
		]);
	}

	public function testRoomInCallChanged() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->participantService->addUsers($room, [[
			'actorType' => 'users',
			'actorId' => $this->userId,
		]]);

		/** @var IUser|MockObject $testUser */
		$testUser = $this->createMock(IUser::class);
		$testUser->expects($this->any())
			->method('getUID')
			->willReturn($this->userId);

		$roomService = $this->createMock(RoomService::class);
		$roomService->method('verifyPassword')
			->willReturn(['result' => true, 'url' => '']);

		$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');
		$userSession = $participant->getSession()->getSessionId();
		$participant = $this->participantService->getParticipantBySession($room, $userSession);

		$this->participantService->changeInCall($room, $participant, Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO);

		$this->assertMessageWasSent($room, [
			'type' => 'incall',
			'incall' => [
				'incall' => 7,
				'changed' => [
					[
						'inCall' => 7,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'nextcloudSessionId' => $userSession,
						'participantType' => Participant::USER,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
						'userId' => $this->userId,
					],
				],
				'users' => [
					[
						'inCall' => 7,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'nextcloudSessionId' => $userSession,
						'participantType' => Participant::USER,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
						'userId' => $this->userId,
					],
				],
			],
		]);

		$this->controller->clearRequests();

		$guestParticipant = $this->participantService->joinRoomAsNewGuest($roomService, $room, '');
		$guestSession = $guestParticipant->getSession()->getSessionId();
		$guestParticipant = $this->participantService->getParticipantBySession($room, $guestSession);
		$this->participantService->changeInCall($room, $guestParticipant, Participant::FLAG_IN_CALL);

		$this->assertMessageWasSent($room, [
			'type' => 'incall',
			'incall' => [
				'incall' => 1,
				'changed' => [
					[
						'inCall' => 1,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'nextcloudSessionId' => $guestSession,
						'participantType' => Participant::GUEST,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
					],
				],
				'users' => [
					[
						'inCall' => 7,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'nextcloudSessionId' => $userSession,
						'participantType' => Participant::USER,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
						'userId' => $this->userId,
					],
					[
						'inCall' => 1,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'nextcloudSessionId' => $guestSession,
						'participantType' => Participant::GUEST,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
					],
				],
			],
		]);

		$this->controller->clearRequests();
		$this->participantService->changeInCall($room, $participant, Participant::FLAG_DISCONNECTED);

		$this->assertMessageWasSent($room, [
			'type' => 'incall',
			'incall' => [
				'incall' => 0,
				'changed' => [
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'nextcloudSessionId' => $userSession,
						'participantType' => Participant::USER,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
						'userId' => $this->userId,
					],
				],
				'users' => [
					[
						'inCall' => 1,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'nextcloudSessionId' => $guestSession,
						'participantType' => Participant::GUEST,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
					],
				],
			],
		]);
	}

	public function testRoomInCallChangedWhenLeavingConversationWhileInCall() {
		/** @var IUser|MockObject $testUser */
		$testUser = $this->createMock(IUser::class);
		$testUser->expects($this->any())
			->method('getUID')
			->willReturn($this->userId);

		$roomService = $this->createMock(RoomService::class);
		$roomService->method('verifyPassword')
			->willReturn(['result' => true, 'url' => '']);

		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->participantService->addUsers($room, [[
			'actorType' => 'users',
			'actorId' => $this->userId,
		]]);
		$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');
		$userSession = $participant->getSession()->getSessionId();
		$this->participantService->changeInCall($room, $participant, Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO);
		$this->controller->clearRequests();
		$this->participantService->leaveRoomAsSession($room, $participant);

		$this->assertMessageWasSent($room, [
			'type' => 'incall',
			'incall' => [
				'incall' => 0,
				'changed' => [
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'nextcloudSessionId' => $userSession,
						'participantType' => Participant::USER,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
						'userId' => $this->userId,
					],
				],
				'users' => [
				],
			],
		]);
	}

	public function testRoomPropertiesEvent(): void {
		$listener = static function (SignalingRoomPropertiesEvent $event) {
			$room = $event->getRoom();
			$event->setProperty('foo', 'bar');
			$event->setProperty('room', $room->getToken());
		};

		$this->dispatcher->addListener(Room::EVENT_BEFORE_SIGNALING_PROPERTIES, $listener);

		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->controller->clearRequests();
		$this->roomService->setName($room, 'Test room');

		$this->assertMessageWasSent($room, [
			'type' => 'update',
			'update' => [
				'userids' => [
				],
				'properties' => [
					'name' => $room->getDisplayName(''),
					'description' => '',
					'type' => $room->getType(),
					'lobby-state' => Webinary::LOBBY_NONE,
					'lobby-timer' => null,
					'read-only' => Room::READ_WRITE,
					'listable' => Room::LISTABLE_NONE,
					'active-since' => null,
					'sip-enabled' => 0,
					'foo' => 'bar',
					'room' => $room->getToken(),
				],
			],
		]);
	}

	public function testParticipantsTypeChanged() {
		$room = $this->manager->createRoom(Room::TYPE_PUBLIC);
		$this->participantService->addUsers($room, [[
			'actorType' => 'users',
			'actorId' => $this->userId,
		]]);

		/** @var IUser|MockObject $testUser */
		$testUser = $this->createMock(IUser::class);
		$testUser->expects($this->any())
			->method('getUID')
			->willReturn($this->userId);

		$roomService = $this->createMock(RoomService::class);
		$roomService->method('verifyPassword')
			->willReturn(['result' => true, 'url' => '']);

		$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');
		$userSession = $participant->getSession()->getSessionId();
		$participant = $this->participantService->getParticipantBySession($room, $userSession);

		$this->participantService->updateParticipantType($room, $participant, Participant::MODERATOR);

		$this->assertMessageWasSent($room, [
			'type' => 'participants',
			'participants' => [
				'changed' => [
					[
						'permissions' => ['publish-audio', 'publish-video', 'publish-screen', 'control'],
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'participantType' => Participant::MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,
						'userId' => $this->userId,
					],
				],
				'users' => [
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'participantType' => Participant::MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,
						'userId' => $this->userId,
					],
				],
			],
		]);

		$this->controller->clearRequests();

		$guestParticipant = $this->participantService->joinRoomAsNewGuest($roomService, $room, '');
		$guestSession = $guestParticipant->getSession()->getSessionId();
		$guestParticipant = $this->participantService->getParticipantBySession($room, $guestSession);

		$this->participantService->updateParticipantType($room, $guestParticipant, Participant::GUEST_MODERATOR);

		$this->assertMessageWasSent($room, [
			'type' => 'participants',
			'participants' => [
				'changed' => [
					[
						'permissions' => ['publish-audio', 'publish-video', 'publish-screen'],
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'participantType' => Participant::GUEST_MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,
					],
				],
				'users' => [
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'participantType' => Participant::MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,
						'userId' => $this->userId,
					],
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'participantType' => Participant::GUEST_MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,
					],
				],
			],
		]);

		$this->controller->clearRequests();
		$notJoinedUserId = 'not-joined-user-id';
		$this->participantService->addUsers($room, [[
			'actorType' => 'users',
			'actorId' => $notJoinedUserId,
		]]);

		$notJoinedParticipant = $this->participantService->getParticipant($room, $notJoinedUserId);
		$this->participantService->updateParticipantType($room, $notJoinedParticipant, Participant::MODERATOR);

		$this->assertMessageWasSent($room, [
			'type' => 'participants',
			'participants' => [
				'changed' => [
				],
				'users' => [
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'participantType' => Participant::MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,
						'userId' => $this->userId,
					],
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => 0,
						'participantType' => Participant::MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,
						'userId' => $notJoinedUserId,
					],
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'participantType' => Participant::GUEST_MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,
					],
				],
			],
		]);

		$this->controller->clearRequests();
		$this->participantService->updateParticipantType($room, $participant, Participant::USER);

		$this->assertMessageWasSent($room, [
			'type' => 'participants',
			'participants' => [
				'changed' => [
					[
						'permissions' => ['publish-audio', 'publish-video', 'publish-screen'],
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'participantType' => Participant::USER,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
						'userId' => $this->userId,
					],
				],
				'users' => [
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'participantType' => Participant::USER,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
						'userId' => $this->userId,
					],
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => 0,
						'participantType' => Participant::MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,
						'userId' => $notJoinedUserId,
					],
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'participantType' => Participant::GUEST_MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,
					],
				],
			],
		]);

		$this->controller->clearRequests();
		$this->participantService->updateParticipantType($room, $guestParticipant, Participant::GUEST);

		$this->assertMessageWasSent($room, [
			'type' => 'participants',
			'participants' => [
				'changed' => [
					[
						'permissions' => ['publish-audio', 'publish-video', 'publish-screen'],
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'participantType' => Participant::GUEST,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
					],
				],
				'users' => [
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'participantType' => Participant::USER,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
						'userId' => $this->userId,
					],
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => 0,
						'participantType' => Participant::MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,
						'userId' => $notJoinedUserId,
					],
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'participantType' => Participant::GUEST,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
					],
				],
			],
		]);

		$this->controller->clearRequests();
		$this->participantService->updatePermissions($room, $guestParticipant, Attendee::PERMISSIONS_MODIFY_SET, Attendee::PERMISSIONS_CUSTOM);

		$this->assertMessageWasSent($room, [
			'type' => 'participants',
			'participants' => [
				'changed' => [
					[
						'permissions' => [],
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'participantType' => Participant::GUEST,
						'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,
					],
				],
				'users' => [
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $userSession,
						'participantType' => Participant::USER,
						'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),
						'userId' => $this->userId,
					],
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => 0,
						'participantType' => Participant::MODERATOR,
						'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,
						'userId' => $notJoinedUserId,
					],
					[
						'inCall' => 0,
						'lastPing' => 0,
						'sessionId' => $guestSession,
						'participantType' => Participant::GUEST,
						'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,
					],
				],
			],
		]);
	}
}

<MSG> Add a const for magic 0 as well

Signed-off-by: Joas Schilling <ab43a7c9cb5b2380afc4ddf8b3e2583169b39a02@schilljs.com>

<DFF> @@ -264,7 +264,7 @@ class BackendNotifierTest extends \Test\TestCase {
 			'userId' => $this->userId,
 			'sessionId' => $userSession,
 		]);
-		$room->changeInCall($userSession, Room::FLAG_IN_CALL | Room::FLAG_WITH_AUDIO | Room::FLAG_WITH_VIDEO);
+		$room->changeInCall($userSession, Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO);
 
 		$requests = $this->controller->getRequests();
 		$bodies = array_map(function($request) use ($room) {
@@ -297,7 +297,7 @@ class BackendNotifierTest extends \Test\TestCase {
 
 		$this->controller->clearRequests();
 		$guestSession = $room->joinRoomGuest('');
-		$room->changeInCall($guestSession, Room::FLAG_IN_CALL);
+		$room->changeInCall($guestSession, Participant::FLAG_IN_CALL);
 
 		$requests = $this->controller->getRequests();
 		$bodies = array_map(function($request) use ($room) {
@@ -334,7 +334,7 @@ class BackendNotifierTest extends \Test\TestCase {
 		], $bodies);
 
 		$this->controller->clearRequests();
-		$room->changeInCall($userSession, 0);
+		$room->changeInCall($userSession, Participant::FLAG_DISCONNECTED);
 
 		$requests = $this->controller->getRequests();
 		$bodies = array_map(function($request) use ($room) {

{'commit': 'diff --git a/js/app.js b/js/app.js\nindex 08db0a71c..acd14eb67 100644\nBinary files a/js/app.js and b/js/app.js differ\ndiff --git a/js/webrtc.js b/js/webrtc.js\nindex 78a901bcd..0440f5f3e 100644\nBinary files a/js/webrtc.js and b/js/webrtc.js differ\ndiff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php\nindex 521910a1d..ff4146805 100644\n--- a/lib/AppInfo/Application.php\n+++ b/lib/AppInfo/Application.php\n@@ -29,6 +29,7 @@ use OCA\\Spreed\\Config;\n use OCA\\Spreed\\GuestManager;\n use OCA\\Spreed\\HookListener;\n use OCA\\Spreed\\Notification\\Notifier;\n+use OCA\\Spreed\\Participant;\n use OCA\\Spreed\\Room;\n use OCA\\Spreed\\Settings\\Personal;\n use OCA\\Spreed\\Signaling\\BackendNotifier;\n@@ -195,7 +196,7 @@ class Application extends App {\n \n \t\t\t$room = $event->getSubject();\n \t\t\t$sessionId = $event->getArgument(\'sessionId\');\n-\t\t\t$notifier->roomInCallChanged($room, 0, [$sessionId]);\n+\t\t\t$notifier->roomInCallChanged($room, Participant::FLAG_DISCONNECTED, [$sessionId]);\n \t\t});\n \t\t$dispatcher->addListener(Room::class . \'::postRemoveBySession\', function(GenericEvent $event) {\n \t\t\t/** @var BackendNotifier $notifier */\ndiff --git a/lib/Controller/CallController.php b/lib/Controller/CallController.php\nindex 28d496237..a46eae640 100644\n--- a/lib/Controller/CallController.php\n+++ b/lib/Controller/CallController.php\n@@ -28,7 +28,7 @@ namespace OCA\\Spreed\\Controller;\n use OCA\\Spreed\\Exceptions\\ParticipantNotFoundException;\n use OCA\\Spreed\\Exceptions\\RoomNotFoundException;\n use OCA\\Spreed\\Manager;\n-use OCA\\Spreed\\Room;\n+use OCA\\Spreed\\Participant;\n use OCA\\Spreed\\TalkSession;\n use OCP\\AppFramework\\Http;\n use OCP\\AppFramework\\Http\\DataResponse;\n@@ -70,7 +70,7 @@ class CallController extends OCSController {\n \t * @param string $token\n \t * @return DataResponse\n \t */\n-\tpublic function getPeersForCall($token) {\n+\tpublic function getPeersForCall(string $token): DataResponse {\n \t\ttry {\n \t\t\t$room = $this->manager->getRoomForSession($this->userId, $this->session->getSessionForRoom($token));\n \t\t} catch (RoomNotFoundException $e) {\n@@ -132,10 +132,10 @@ class CallController extends OCSController {\n \t * @UseSession\n \t *\n \t * @param string $token\n-\t * @param int $flags\n+\t * @param int|null $flags\n \t * @return DataResponse\n \t */\n-\tpublic function joinCall($token, $flags) {\n+\tpublic function joinCall(string $token, $flags): DataResponse {\n \t\ttry {\n \t\t\t$room = $this->manager->getRoomForParticipantByToken($token, $this->userId);\n \t\t} catch (RoomNotFoundException $e) {\n@@ -167,7 +167,7 @@ class CallController extends OCSController {\n \n \t\tif ($flags === null) {\n \t\t\t// Default flags: user is in room with audio/video.\n-\t\t\t$flags = Room::FLAG_IN_CALL | Room::FLAG_WITH_AUDIO | Room::FLAG_WITH_VIDEO;\n+\t\t\t$flags = Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO;\n \t\t}\n \n \t\t$room->changeInCall($sessionId, $flags);\n@@ -182,7 +182,7 @@ class CallController extends OCSController {\n \t * @param string $token\n \t * @return DataResponse\n \t */\n-\tpublic function leaveCall($token) {\n+\tpublic function leaveCall(string $token): DataResponse {\n \t\ttry {\n \t\t\t$room = $this->manager->getRoomForParticipantByToken($token, $this->userId);\n \t\t} catch (RoomNotFoundException $e) {\n@@ -212,7 +212,7 @@ class CallController extends OCSController {\n \t\t\treturn new DataResponse([], Http::STATUS_NOT_FOUND);\n \t\t}\n \n-\t\t$room->changeInCall($sessionId, 0);\n+\t\t$room->changeInCall($sessionId, Participant::FLAG_DISCONNECTED);\n \n \t\treturn new DataResponse();\n \t}\ndiff --git a/lib/Participant.php b/lib/Participant.php\nindex cd19fa019..f53f98d90 100644\n--- a/lib/Participant.php\n+++ b/lib/Participant.php\n@@ -33,6 +33,11 @@ class Participant {\n \tconst GUEST = 4;\n \tconst USER_SELF_JOINED = 5;\n \n+\tconst FLAG_DISCONNECTED = 0;\n+\tconst FLAG_IN_CALL = 1;\n+\tconst FLAG_WITH_AUDIO = 2;\n+\tconst FLAG_WITH_VIDEO = 4;\n+\n \t/** @var IDBConnection */\n \tprotected $db;\n \t/** @var Room */\ndiff --git a/lib/Room.php b/lib/Room.php\nindex aa9c0ec32..25afadfe4 100644\n--- a/lib/Room.php\n+++ b/lib/Room.php\n@@ -42,12 +42,6 @@ class Room {\n \tconst GROUP_CALL = 2;\n \tconst PUBLIC_CALL = 3;\n \n-\t// Bit flags for \'in_call\' status. Must stay in sync with values in\n-\t// "js/app.js".\n-\tconst FLAG_IN_CALL = 1;\n-\tconst FLAG_WITH_AUDIO = 2;\n-\tconst FLAG_WITH_VIDEO = 4;\n-\n \t/** @var Manager */\n \tprivate $manager;\n \t/** @var IDBConnection */\n@@ -684,15 +678,8 @@ class Room {\n \t}\n \n \n-\t/**\n-\t * @param string $sessionId\n-\t * @param int $flags\n-\t */\n-\tpublic function changeInCall($sessionId, $flags) {\n-\t\tif ($flags === false) {\n-\t\t\t$flags = 0;\n-\t\t}\n-\t\tif ($flags !== 0) {\n+\tpublic function changeInCall(string $sessionId, int $flags) {\n+\t\tif ($flags !== Participant::FLAG_DISCONNECTED) {\n \t\t\t$this->dispatcher->dispatch(self::class . \'::preSessionJoinCall\', new GenericEvent($this, [\n \t\t\t\t\'sessionId\' => $sessionId,\n \t\t\t\t\'flags\' => $flags,\n@@ -710,7 +697,7 @@ class Room {\n \t\t\t->andWhere($query->expr()->eq(\'room_id\', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));\n \t\t$query->execute();\n \n-\t\tif ($flags !== 0) {\n+\t\tif ($flags !== Participant::FLAG_DISCONNECTED) {\n \t\t\t$this->dispatcher->dispatch(self::class . \'::postSessionJoinCall\', new GenericEvent($this, [\n \t\t\t\t\'sessionId\' => $sessionId,\n \t\t\t\t\'flags\' => $flags,\n@@ -866,7 +853,7 @@ class Room {\n \t\t$query->select(\'session_id\')\n \t\t\t->from(\'talk_participants\')\n \t\t\t->where($query->expr()->eq(\'room_id\', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)))\n-\t\t\t->andWhere($query->expr()->neq(\'in_call\', $query->createNamedParameter(0, IQueryBuilder::PARAM_INT)))\n+\t\t\t->andWhere($query->expr()->neq(\'in_call\', $query->createNamedParameter(Participant::FLAG_DISCONNECTED, IQueryBuilder::PARAM_INT)))\n \t\t\t->setMaxResults(1);\n \t\t$result = $query->execute();\n \t\t$row = $result->fetch();\ndiff --git a/lib/Signaling/BackendNotifier.php b/lib/Signaling/BackendNotifier.php\nindex f6c6db8bc..78d19599b 100644\n--- a/lib/Signaling/BackendNotifier.php\n+++ b/lib/Signaling/BackendNotifier.php\n@@ -287,7 +287,7 @@ class BackendNotifier{\n \t\t$participants = $room->getParticipants();\n \t\tforeach ($participants[\'users\'] as $userId => $participant) {\n \t\t\t$participant[\'userId\'] = $userId;\n-\t\t\tif ($participant[\'inCall\'] !== 0) {\n+\t\t\tif ($participant[\'inCall\'] !== Participant::FLAG_DISCONNECTED) {\n \t\t\t\t$users[] = $participant;\n \t\t\t}\n \t\t\tif (in_array($participant[\'sessionId\'], $sessionIds)) {\n@@ -298,7 +298,7 @@ class BackendNotifier{\n \t\t\tif (!isset($participant[\'participantType\'])) {\n \t\t\t\t$participant[\'participantType\'] = Participant::GUEST;\n \t\t\t}\n-\t\t\tif ($participant[\'inCall\'] !== 0) {\n+\t\t\tif ($participant[\'inCall\'] !== Participant::FLAG_DISCONNECTED) {\n \t\t\t\t$users[] = $participant;\n \t\t\t}\n \t\t\tif (in_array($participant[\'sessionId\'], $sessionIds)) {\ndiff --git a/tests/php/Signaling/BackendNotifierTest.php b/tests/php/Signaling/BackendNotifierTest.php\nindex d8fe75c65..6a2244310 100644\n--- a/tests/php/Signaling/BackendNotifierTest.php\n+++ b/tests/php/Signaling/BackendNotifierTest.php\n@@ -264,7 +264,7 @@ class BackendNotifierTest extends \\Test\\TestCase {\n \t\t\t\'userId\' => $this->userId,\n \t\t\t\'sessionId\' => $userSession,\n \t\t]);\n-\t\t$room->changeInCall($userSession, Room::FLAG_IN_CALL | Room::FLAG_WITH_AUDIO | Room::FLAG_WITH_VIDEO);\n+\t\t$room->changeInCall($userSession, Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO);\n \n \t\t$requests = $this->controller->getRequests();\n \t\t$bodies = array_map(function($request) use ($room) {\n@@ -297,7 +297,7 @@ class BackendNotifierTest extends \\Test\\TestCase {\n \n \t\t$this->controller->clearRequests();\n \t\t$guestSession = $room->joinRoomGuest(\'\');\n-\t\t$room->changeInCall($guestSession, Room::FLAG_IN_CALL);\n+\t\t$room->changeInCall($guestSession, Participant::FLAG_IN_CALL);\n \n \t\t$requests = $this->controller->getRequests();\n \t\t$bodies = array_map(function($request) use ($room) {\n@@ -334,7 +334,7 @@ class BackendNotifierTest extends \\Test\\TestCase {\n \t\t], $bodies);\n \n \t\t$this->controller->clearRequests();\n-\t\t$room->changeInCall($userSession, 0);\n+\t\t$room->changeInCall($userSession, Participant::FLAG_DISCONNECTED);\n \n \t\t$requests = $this->controller->getRequests();\n \t\t$bodies = array_map(function($request) use ($room) {\n', 'commit_subject': 'Add a const for magic 0 as well', 'repo_name': 'nextcloud/spreed', 'committer': {'name': 'Joachim Bauch', 'email': '1d6e1cf70ec6f9ab28d3ea4b27a49a77654d370e@joachim-bauch.de', 'time_sec': '1535100740', 'tz_offset': '120', 'date': {'seconds': '1535100740'}}, 'commit_tree': '571b8aba4571abd2c079a2908339187a62f26853', 'commit_message': 'Add a const for magic 0 as well\n\nSigned-off-by: Joas Schilling <ab43a7c9cb5b2380afc4ddf8b3e2583169b39a02@schilljs.com>\n', 'language_name': 'JavaScript', 'license': 'agpl-3.0', 'hunks': "@@ -264,7 +264,7 @@ class BackendNotifierTest extends \\Test\\TestCase {\n \t\t\t'userId' => $this->userId,\n \t\t\t'sessionId' => $userSession,\n \t\t]);\n-\t\t$room->changeInCall($userSession, Room::FLAG_IN_CALL | Room::FLAG_WITH_AUDIO | Room::FLAG_WITH_VIDEO);\n+\t\t$room->changeInCall($userSession, Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO);\n \n \t\t$requests = $this->controller->getRequests();\n \t\t$bodies = array_map(function($request) use ($room) {\n@@ -297,7 +297,7 @@ class BackendNotifierTest extends \\Test\\TestCase {\n \n \t\t$this->controller->clearRequests();\n \t\t$guestSession = $room->joinRoomGuest('');\n-\t\t$room->changeInCall($guestSession, Room::FLAG_IN_CALL);\n+\t\t$room->changeInCall($guestSession, Participant::FLAG_IN_CALL);\n \n \t\t$requests = $this->controller->getRequests();\n \t\t$bodies = array_map(function($request) use ($room) {\n@@ -334,7 +334,7 @@ class BackendNotifierTest extends \\Test\\TestCase {\n \t\t], $bodies);\n \n \t\t$this->controller->clearRequests();\n-\t\t$room->changeInCall($userSession, 0);\n+\t\t$room->changeInCall($userSession, Participant::FLAG_DISCONNECTED);\n \n \t\t$requests = $this->controller->getRequests();\n \t\t$bodies = array_map(function($request) use ($room) {\n", 'addition_count': 3, 'deletion_count': 3, 'src_file': 'a/tests/php/Signaling/BackendNotifierTest.php', 'tgt_file': 'b/tests/php/Signaling/BackendNotifierTest.php', 'file_extension': '.php', 'before_file': "<?php\n/**\n *\n * @copyright Copyright (c) 2018, Joachim Bauch (bauch@struktur.de)\n *\n * @license GNU AGPL version 3 or any later version\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Affero General Public License as\n * published by the Free Software Foundation, either version 3 of the\n * License, or (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Affero General Public License for more details.\n *\n * You should have received a copy of the GNU Affero General Public License\n * along with this program.  If not, see <http://www.gnu.org/licenses/>.\n *\n */\n\nnamespace OCA\\Talk\\Tests\\php\\Signaling;\n\nuse OCA\\Talk\\AppInfo\\Application;\nuse OCA\\Talk\\Chat\\CommentsManager;\nuse OCA\\Talk\\Config;\nuse OCA\\Talk\\Events\\SignalingRoomPropertiesEvent;\nuse OCA\\Talk\\Manager;\nuse OCA\\Talk\\Model\\Attendee;\nuse OCA\\Talk\\Model\\AttendeeMapper;\nuse OCA\\Talk\\Model\\SessionMapper;\nuse OCA\\Talk\\Participant;\nuse OCA\\Talk\\Room;\nuse OCA\\Talk\\Service\\ParticipantService;\nuse OCA\\Talk\\Service\\RoomService;\nuse OCA\\Talk\\Signaling\\BackendNotifier;\nuse OCA\\Talk\\TalkSession;\nuse OCA\\Talk\\Webinary;\nuse OCP\\App\\IAppManager;\nuse OCP\\AppFramework\\Utility\\ITimeFactory;\nuse OCP\\BackgroundJob\\IJobList;\nuse OCP\\EventDispatcher\\IEventDispatcher;\nuse OCP\\Http\\Client\\IClientService;\nuse OCP\\IGroupManager;\nuse OCP\\IL10N;\nuse OCP\\IURLGenerator;\nuse OCP\\IUser;\nuse OCP\\IUserManager;\nuse OCP\\Security\\IHasher;\nuse OCP\\Security\\ISecureRandom;\nuse OCP\\Share\\IManager;\nuse PHPUnit\\Framework\\MockObject\\MockObject;\nuse Psr\\Log\\LoggerInterface;\nuse Test\\TestCase;\n\nclass CustomBackendNotifier extends BackendNotifier {\n\tprivate array $requests = [];\n\n\tpublic function getRequests(): array {\n\t\treturn $this->requests;\n\t}\n\n\tpublic function clearRequests() {\n\t\t$this->requests = [];\n\t}\n\n\tprotected function doRequest(string $url, array $params, int $retries = 3): void {\n\t\t$this->requests[] = [\n\t\t\t'url' => $url,\n\t\t\t'params' => $params,\n\t\t];\n\t}\n}\n\n/**\n * @group DB\n */\nclass BackendNotifierTest extends TestCase {\n\tprivate ?Config $config = null;\n\tprivate ?ISecureRandom $secureRandom = null;\n\t/** @var ITimeFactory|MockObject */\n\tprivate $timeFactory;\n\t/** @var ParticipantService|MockObject */\n\tprivate $participantService;\n\t/** @var \\OCA\\Talk\\Signaling\\Manager|MockObject */\n\tprivate $signalingManager;\n\t/** @var IURLGenerator|MockObject */\n\tprivate $urlGenerator;\n\tprivate ?\\OCA\\Talk\\Tests\\php\\Signaling\\CustomBackendNotifier $controller = null;\n\n\tprivate ?Manager $manager = null;\n\tprivate ?RoomService $roomService = null;\n\n\tprivate ?string $userId = null;\n\tprivate ?string $signalingSecret = null;\n\tprivate ?string $baseUrl = null;\n\n\tprotected Application $app;\n\tprotected BackendNotifier $originalBackendNotifier;\n\tprivate ?IEventDispatcher $dispatcher = null;\n\t/** @var IJobList|MockObject */\n\tprivate IJobList $jobList;\n\n\tpublic function setUp(): void {\n\t\tparent::setUp();\n\n\t\t$this->userId = 'testUser';\n\t\t$this->secureRandom = \\OC::$server->getSecureRandom();\n\t\t$this->timeFactory = $this->createMock(ITimeFactory::class);\n\t\t$this->urlGenerator = $this->createMock(IURLGenerator::class);\n\t\t$groupManager = $this->createMock(IGroupManager::class);\n\t\t$userManager = $this->createMock(IUserManager::class);\n\t\t$config = \\OC::$server->getConfig();\n\t\t$this->signalingSecret = 'the-signaling-secret';\n\t\t$this->baseUrl = 'https://localhost/signaling';\n\t\t$config->setAppValue('spreed', 'signaling_servers', json_encode([\n\t\t\t'secret' => $this->signalingSecret,\n\t\t\t'servers' => [\n\t\t\t\t[\n\t\t\t\t\t'server' => $this->baseUrl,\n\t\t\t\t],\n\t\t\t],\n\t\t]));\n\n\t\t$this->participantService = \\OC::$server->get(ParticipantService::class);\n\t\t$this->signalingManager = $this->createMock(\\OCA\\Talk\\Signaling\\Manager::class);\n\t\t$this->signalingManager->expects($this->any())\n\t\t\t->method('getSignalingServerForConversation')\n\t\t\t->willReturn(['server' => $this->baseUrl]);\n\n\t\t$this->dispatcher = \\OC::$server->get(IEventDispatcher::class);\n\t\t$this->config = new Config($config, $this->secureRandom, $groupManager, $userManager, $this->urlGenerator, $this->timeFactory, $this->dispatcher);\n\t\t$this->recreateBackendNotifier();\n\n\t\t$this->overwriteService(BackendNotifier::class, $this->controller);\n\n\t\t$dbConnection = \\OC::$server->getDatabaseConnection();\n\t\t$this->manager = new Manager(\n\t\t\t$dbConnection,\n\t\t\t$config,\n\t\t\t$this->config,\n\t\t\t\\OC::$server->get(IAppManager::class),\n\t\t\t\\OC::$server->get(AttendeeMapper::class),\n\t\t\t\\OC::$server->get(SessionMapper::class),\n\t\t\t$this->participantService,\n\t\t\t$this->secureRandom,\n\t\t\t$this->createMock(IUserManager::class),\n\t\t\t$groupManager,\n\t\t\t$this->createMock(CommentsManager::class),\n\t\t\t$this->createMock(TalkSession::class),\n\t\t\t$this->dispatcher,\n\t\t\t$this->timeFactory,\n\t\t\t$this->createMock(IHasher::class),\n\t\t\t$this->createMock(IL10N::class)\n\t\t);\n\t\t$this->jobList = $this->createMock(IJobList::class);\n\n\t\t$this->roomService = new RoomService(\n\t\t\t$this->manager,\n\t\t\t$this->participantService,\n\t\t\t$dbConnection,\n\t\t\t$this->timeFactory,\n\t\t\t$this->createMock(IManager::class),\n\t\t\t$this->createMock(IHasher::class),\n\t\t\t$this->dispatcher,\n\t\t\t$this->jobList\n\t\t);\n\t}\n\n\tpublic function tearDown(): void {\n\t\t$config = \\OC::$server->getConfig();\n\t\t$config->deleteAppValue('spreed', 'signaling_servers');\n\t\t$this->restoreService(BackendNotifier::class);\n\t\tparent::tearDown();\n\t}\n\n\tprivate function recreateBackendNotifier() {\n\t\t$this->controller = new CustomBackendNotifier(\n\t\t\t$this->config,\n\t\t\t$this->createMock(LoggerInterface::class),\n\t\t\t$this->createMock(IClientService::class),\n\t\t\t$this->secureRandom,\n\t\t\t$this->signalingManager,\n\t\t\t$this->participantService,\n\t\t\t$this->urlGenerator\n\t\t);\n\t}\n\n\tprivate function calculateBackendChecksum($data, $random) {\n\t\tif (empty($random) || strlen($random) < 32) {\n\t\t\treturn false;\n\t\t}\n\t\treturn hash_hmac('sha256', $random . $data, $this->signalingSecret);\n\t}\n\n\tprivate function validateBackendRequest($expectedUrl, $request) {\n\t\t$this->assertTrue(isset($request));\n\t\t$this->assertEquals($expectedUrl, $request['url']);\n\t\t$headers = $request['params']['headers'];\n\t\t$this->assertEquals('application/json', $headers['Content-Type']);\n\t\t$random = $headers['Spreed-Signaling-Random'];\n\t\t$checksum = $headers['Spreed-Signaling-Checksum'];\n\t\t$body = $request['params']['body'];\n\t\t$this->assertEquals($this->calculateBackendChecksum($body, $random), $checksum);\n\t\treturn $body;\n\t}\n\n\tprivate function assertMessageWasSent(Room $room, array $message): void {\n\t\t$requests = $this->controller->getRequests();\n\t\t$bodies = array_map(function ($request) use ($room) {\n\t\t\treturn json_decode($this->validateBackendRequest($this->baseUrl . '/api/v1/room/' . $room->getToken(), $request), true);\n\t\t}, $requests);\n\n\t\t$bodies = array_filter($bodies, function (array $body) use ($message) {\n\t\t\treturn $body['type'] === $message['type'];\n\t\t});\n\n\t\t$bodies = array_map([$this, 'sortParticipantUsers'], $bodies);\n\t\t$message = $this->sortParticipantUsers($message);\n\t\t$this->assertContainsEquals($message, $bodies, json_encode($bodies, JSON_PRETTY_PRINT));\n\t}\n\n\tprivate function assertNoMessageOfTypeWasSent(Room $room, string $messageType): void {\n\t\t$requests = $this->controller->getRequests();\n\t\t$bodies = array_map(function ($request) use ($room) {\n\t\t\treturn json_decode($this->validateBackendRequest($this->baseUrl . '/api/v1/room/' . $room->getToken(), $request), true);\n\t\t}, $requests);\n\n\t\t$bodies = array_filter($bodies, function (array $body) use ($messageType) {\n\t\t\treturn $body['type'] === $messageType;\n\t\t});\n\n\t\t$this->assertEmpty($bodies);\n\t}\n\n\tprivate function sortParticipantUsers(array $message): array {\n\t\tif ($message['type'] === 'participants') {\n\t\t\tusort($message['participants']['users'], static function ($a, $b) {\n\t\t\t\treturn\n\t\t\t\t\t[$a['userId'] ?? '', $a['participantType'], $a['sessionId'], $a['lastPing']]\n\t\t\t\t\t<=>\n\t\t\t\t\t[$b['userId'] ?? '', $b['participantType'], $b['sessionId'], $b['lastPing']]\n\t\t\t\t;\n\t\t\t});\n\t\t}\n\t\tif ($message['type'] === 'incall') {\n\t\t\tusort($message['incall']['users'], static function ($a, $b) {\n\t\t\t\treturn\n\t\t\t\t\t[$a['userId'] ?? '', $a['participantType'], $a['sessionId'], $a['lastPing']]\n\t\t\t\t\t<=>\n\t\t\t\t\t[$b['userId'] ?? '', $b['participantType'], $b['sessionId'], $b['lastPing']]\n\t\t\t\t;\n\t\t\t});\n\t\t}\n\t\treturn $message;\n\t}\n\n\tpublic function testRoomInvite() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->participantService->addUsers($room, [[\n\t\t\t'actorType' => 'users',\n\t\t\t'actorId' => $this->userId,\n\t\t\t'userId' => $this->userId,\n\t\t\t'sessionId' => $userSession,\n\t\t]);\n\t\t$room->changeInCall($userSession, Room::FLAG_IN_CALL | Room::FLAG_WITH_AUDIO | Room::FLAG_WITH_VIDEO);\n\n\t\t$requests = $this->controller->getRequests();\n\t\t$bodies = array_map(function($request) use ($room) {\n\t\t\t\t],\n\t\t\t\t'alluserids' => [\n\t\t\t\t\t$this->userId,\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t\t'participant-list' => 'refresh',\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomDisinvite() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->participantService->addUsers($room, [[\n\t\t\t'actorType' => 'users',\n\t\t\t'actorId' => $this->userId,\n\t\t]]);\n\t\t$this->controller->clearRequests();\n\n\t\t$this->controller->clearRequests();\n\t\t$guestSession = $room->joinRoomGuest('');\n\t\t$room->changeInCall($guestSession, Room::FLAG_IN_CALL);\n\n\t\t$requests = $this->controller->getRequests();\n\t\t$bodies = array_map(function($request) use ($room) {\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'disinvite',\n\t\t\t'disinvite' => [\n\t\t\t\t'userids' => [\n\t\t\t\t\t$this->userId,\n\t\t\t\t],\n\t\t\t\t'alluserids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t\t'participant-list' => 'refresh',\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomDisinviteOnRemovalOfGuest() {\n\t\t$roomService = $this->createMock(RoomService::class);\n\t\t$roomService->method('verifyPassword')\n\t\t\t->willReturn(['result' => true, 'url' => '']);\n\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$participant = $this->participantService->joinRoomAsNewGuest($roomService, $room, '');\n\t\t], $bodies);\n\n\t\t$this->controller->clearRequests();\n\t\t$room->changeInCall($userSession, 0);\n\n\t\t$requests = $this->controller->getRequests();\n\t\t$bodies = array_map(function($request) use ($room) {\n\t\t\t\t\t$participant->getSession()->getSessionId(),\n\t\t\t\t],\n\t\t\t\t'alluserids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t\t'participant-list' => 'refresh',\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testNoRoomDisinviteOnLeaveOfNormalUser() {\n\t\t/** @var IUser|MockObject $testUser */\n\t\t$testUser = $this->createMock(IUser::class);\n\t\t$testUser->expects($this->any())\n\t\t\t->method('getUID')\n\t\t\t->willReturn($this->userId);\n\n\t\t$roomService = $this->createMock(RoomService::class);\n\t\t$roomService->method('verifyPassword')\n\t\t\t->willReturn(['result' => true, 'url' => '']);\n\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->participantService->addUsers($room, [[\n\t\t\t'actorType' => 'users',\n\t\t\t'actorId' => $this->userId,\n\t\t]]);\n\t\t$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');\n\t\t$this->controller->clearRequests();\n\t\t$this->participantService->leaveRoomAsSession($room, $participant);\n\n\t\t$this->assertNoMessageOfTypeWasSent($room, 'disinvite');\n\t}\n\n\tpublic function testRoomDisinviteOnLeaveOfNormalUserWithDuplicatedSession() {\n\t\t/** @var IUser|MockObject $testUser */\n\t\t$testUser = $this->createMock(IUser::class);\n\t\t$testUser->expects($this->any())\n\t\t\t->method('getUID')\n\t\t\t->willReturn($this->userId);\n\n\t\t$roomService = $this->createMock(RoomService::class);\n\t\t$roomService->method('verifyPassword')\n\t\t\t->willReturn(['result' => true, 'url' => '']);\n\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->participantService->addUsers($room, [[\n\t\t\t'actorType' => 'users',\n\t\t\t'actorId' => $this->userId,\n\t\t]]);\n\t\t$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');\n\t\t$this->controller->clearRequests();\n\t\t$this->participantService->leaveRoomAsSession($room, $participant, true);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'disinvite',\n\t\t\t'disinvite' => [\n\t\t\t\t'sessionids' => [\n\t\t\t\t\t$participant->getSession()->getSessionId(),\n\t\t\t\t],\n\t\t\t\t'alluserids' => [\n\t\t\t\t\t$this->userId,\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t\t'participant-list' => 'refresh',\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomDisinviteOnLeaveOfSelfJoinedUser() {\n\t\t/** @var IUser|MockObject $testUser */\n\t\t$testUser = $this->createMock(IUser::class);\n\t\t$testUser->expects($this->any())\n\t\t\t->method('getUID')\n\t\t\t->willReturn($this->userId);\n\n\t\t$roomService = $this->createMock(RoomService::class);\n\t\t$roomService->method('verifyPassword')\n\t\t\t->willReturn(['result' => true, 'url' => '']);\n\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');\n\t\t$this->controller->clearRequests();\n\t\t$this->participantService->leaveRoomAsSession($room, $participant);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'disinvite',\n\t\t\t'disinvite' => [\n\t\t\t\t'userids' => [\n\t\t\t\t\t$this->userId,\n\t\t\t\t],\n\t\t\t\t'alluserids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t\t'participant-list' => 'refresh',\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomDisinviteOnLeaveOfGuest() {\n\t\t$roomService = $this->createMock(RoomService::class);\n\t\t$roomService->method('verifyPassword')\n\t\t\t->willReturn(['result' => true, 'url' => '']);\n\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$participant = $this->participantService->joinRoomAsNewGuest($roomService, $room, '');\n\t\t$this->controller->clearRequests();\n\t\t$this->participantService->leaveRoomAsSession($room, $participant);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'disinvite',\n\t\t\t'disinvite' => [\n\t\t\t\t'sessionids' => [\n\t\t\t\t\t$participant->getSession()->getSessionId(),\n\t\t\t\t],\n\t\t\t\t'alluserids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t\t'participant-list' => 'refresh',\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomNameChanged() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->roomService->setName($room, 'Test room');\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'update',\n\t\t\t'update' => [\n\t\t\t\t'userids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'description' => '',\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomDescriptionChanged() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->roomService->setDescription($room, 'The description');\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'update',\n\t\t\t'update' => [\n\t\t\t\t'userids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'description' => 'The description',\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomPasswordChanged() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->roomService->setPassword($room, 'password');\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'update',\n\t\t\t'update' => [\n\t\t\t\t'userids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'description' => '',\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomTypeChanged() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->roomService->setType($room, Room::TYPE_GROUP);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'update',\n\t\t\t'update' => [\n\t\t\t\t'userids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'description' => '',\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomReadOnlyChanged() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->roomService->setReadOnly($room, Room::READ_ONLY);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'update',\n\t\t\t'update' => [\n\t\t\t\t'userids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'description' => '',\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_ONLY,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomListableChanged() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->roomService->setListable($room, Room::LISTABLE_ALL);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'update',\n\t\t\t'update' => [\n\t\t\t\t'userids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_ALL,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t\t'description' => '',\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomLobbyStateChanged() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->roomService->setLobby($room, Webinary::LOBBY_NON_MODERATORS, null);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'update',\n\t\t\t'update' => [\n\t\t\t\t'userids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'description' => '',\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NON_MODERATORS,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomDelete(): void {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->participantService->addUsers($room, [[\n\t\t\t'actorType' => 'users',\n\t\t\t'actorId' => $this->userId,\n\t\t]]);\n\t\t$this->roomService->deleteRoom($room);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'delete',\n\t\t\t'delete' => [\n\t\t\t\t'userids' => [\n\t\t\t\t\t$this->userId,\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomInCallChanged() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->participantService->addUsers($room, [[\n\t\t\t'actorType' => 'users',\n\t\t\t'actorId' => $this->userId,\n\t\t]]);\n\n\t\t/** @var IUser|MockObject $testUser */\n\t\t$testUser = $this->createMock(IUser::class);\n\t\t$testUser->expects($this->any())\n\t\t\t->method('getUID')\n\t\t\t->willReturn($this->userId);\n\n\t\t$roomService = $this->createMock(RoomService::class);\n\t\t$roomService->method('verifyPassword')\n\t\t\t->willReturn(['result' => true, 'url' => '']);\n\n\t\t$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');\n\t\t$userSession = $participant->getSession()->getSessionId();\n\t\t$participant = $this->participantService->getParticipantBySession($room, $userSession);\n\n\t\t$this->participantService->changeInCall($room, $participant, Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'incall',\n\t\t\t'incall' => [\n\t\t\t\t'incall' => 7,\n\t\t\t\t'changed' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 7,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'nextcloudSessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::USER,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 7,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'nextcloudSessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::USER,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\n\t\t$this->controller->clearRequests();\n\n\t\t$guestParticipant = $this->participantService->joinRoomAsNewGuest($roomService, $room, '');\n\t\t$guestSession = $guestParticipant->getSession()->getSessionId();\n\t\t$guestParticipant = $this->participantService->getParticipantBySession($room, $guestSession);\n\t\t$this->participantService->changeInCall($room, $guestParticipant, Participant::FLAG_IN_CALL);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'incall',\n\t\t\t'incall' => [\n\t\t\t\t'incall' => 1,\n\t\t\t\t'changed' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 1,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'nextcloudSessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 7,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'nextcloudSessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::USER,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 1,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'nextcloudSessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\n\t\t$this->controller->clearRequests();\n\t\t$this->participantService->changeInCall($room, $participant, Participant::FLAG_DISCONNECTED);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'incall',\n\t\t\t'incall' => [\n\t\t\t\t'incall' => 0,\n\t\t\t\t'changed' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'nextcloudSessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::USER,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 1,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'nextcloudSessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomInCallChangedWhenLeavingConversationWhileInCall() {\n\t\t/** @var IUser|MockObject $testUser */\n\t\t$testUser = $this->createMock(IUser::class);\n\t\t$testUser->expects($this->any())\n\t\t\t->method('getUID')\n\t\t\t->willReturn($this->userId);\n\n\t\t$roomService = $this->createMock(RoomService::class);\n\t\t$roomService->method('verifyPassword')\n\t\t\t->willReturn(['result' => true, 'url' => '']);\n\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->participantService->addUsers($room, [[\n\t\t\t'actorType' => 'users',\n\t\t\t'actorId' => $this->userId,\n\t\t]]);\n\t\t$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');\n\t\t$userSession = $participant->getSession()->getSessionId();\n\t\t$this->participantService->changeInCall($room, $participant, Participant::FLAG_IN_CALL | Participant::FLAG_WITH_AUDIO | Participant::FLAG_WITH_VIDEO);\n\t\t$this->controller->clearRequests();\n\t\t$this->participantService->leaveRoomAsSession($room, $participant);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'incall',\n\t\t\t'incall' => [\n\t\t\t\t'incall' => 0,\n\t\t\t\t'changed' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'nextcloudSessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::USER,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testRoomPropertiesEvent(): void {\n\t\t$listener = static function (SignalingRoomPropertiesEvent $event) {\n\t\t\t$room = $event->getRoom();\n\t\t\t$event->setProperty('foo', 'bar');\n\t\t\t$event->setProperty('room', $room->getToken());\n\t\t};\n\n\t\t$this->dispatcher->addListener(Room::EVENT_BEFORE_SIGNALING_PROPERTIES, $listener);\n\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->controller->clearRequests();\n\t\t$this->roomService->setName($room, 'Test room');\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'update',\n\t\t\t'update' => [\n\t\t\t\t'userids' => [\n\t\t\t\t],\n\t\t\t\t'properties' => [\n\t\t\t\t\t'name' => $room->getDisplayName(''),\n\t\t\t\t\t'description' => '',\n\t\t\t\t\t'type' => $room->getType(),\n\t\t\t\t\t'lobby-state' => Webinary::LOBBY_NONE,\n\t\t\t\t\t'lobby-timer' => null,\n\t\t\t\t\t'read-only' => Room::READ_WRITE,\n\t\t\t\t\t'listable' => Room::LISTABLE_NONE,\n\t\t\t\t\t'active-since' => null,\n\t\t\t\t\t'sip-enabled' => 0,\n\t\t\t\t\t'foo' => 'bar',\n\t\t\t\t\t'room' => $room->getToken(),\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n\n\tpublic function testParticipantsTypeChanged() {\n\t\t$room = $this->manager->createRoom(Room::TYPE_PUBLIC);\n\t\t$this->participantService->addUsers($room, [[\n\t\t\t'actorType' => 'users',\n\t\t\t'actorId' => $this->userId,\n\t\t]]);\n\n\t\t/** @var IUser|MockObject $testUser */\n\t\t$testUser = $this->createMock(IUser::class);\n\t\t$testUser->expects($this->any())\n\t\t\t->method('getUID')\n\t\t\t->willReturn($this->userId);\n\n\t\t$roomService = $this->createMock(RoomService::class);\n\t\t$roomService->method('verifyPassword')\n\t\t\t->willReturn(['result' => true, 'url' => '']);\n\n\t\t$participant = $this->participantService->joinRoom($roomService, $room, $testUser, '');\n\t\t$userSession = $participant->getSession()->getSessionId();\n\t\t$participant = $this->participantService->getParticipantBySession($room, $userSession);\n\n\t\t$this->participantService->updateParticipantType($room, $participant, Participant::MODERATOR);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'participants',\n\t\t\t'participants' => [\n\t\t\t\t'changed' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'permissions' => ['publish-audio', 'publish-video', 'publish-screen', 'control'],\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\n\t\t$this->controller->clearRequests();\n\n\t\t$guestParticipant = $this->participantService->joinRoomAsNewGuest($roomService, $room, '');\n\t\t$guestSession = $guestParticipant->getSession()->getSessionId();\n\t\t$guestParticipant = $this->participantService->getParticipantBySession($room, $guestSession);\n\n\t\t$this->participantService->updateParticipantType($room, $guestParticipant, Participant::GUEST_MODERATOR);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'participants',\n\t\t\t'participants' => [\n\t\t\t\t'changed' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'permissions' => ['publish-audio', 'publish-video', 'publish-screen'],\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST_MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST_MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\n\t\t$this->controller->clearRequests();\n\t\t$notJoinedUserId = 'not-joined-user-id';\n\t\t$this->participantService->addUsers($room, [[\n\t\t\t'actorType' => 'users',\n\t\t\t'actorId' => $notJoinedUserId,\n\t\t]]);\n\n\t\t$notJoinedParticipant = $this->participantService->getParticipant($room, $notJoinedUserId);\n\t\t$this->participantService->updateParticipantType($room, $notJoinedParticipant, Participant::MODERATOR);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'participants',\n\t\t\t'participants' => [\n\t\t\t\t'changed' => [\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => 0,\n\t\t\t\t\t\t'participantType' => Participant::MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,\n\t\t\t\t\t\t'userId' => $notJoinedUserId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST_MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\n\t\t$this->controller->clearRequests();\n\t\t$this->participantService->updateParticipantType($room, $participant, Participant::USER);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'participants',\n\t\t\t'participants' => [\n\t\t\t\t'changed' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'permissions' => ['publish-audio', 'publish-video', 'publish-screen'],\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::USER,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::USER,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => 0,\n\t\t\t\t\t\t'participantType' => Participant::MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,\n\t\t\t\t\t\t'userId' => $notJoinedUserId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST_MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_MAX_DEFAULT,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\n\t\t$this->controller->clearRequests();\n\t\t$this->participantService->updateParticipantType($room, $guestParticipant, Participant::GUEST);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'participants',\n\t\t\t'participants' => [\n\t\t\t\t'changed' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'permissions' => ['publish-audio', 'publish-video', 'publish-screen'],\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::USER,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => 0,\n\t\t\t\t\t\t'participantType' => Participant::MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,\n\t\t\t\t\t\t'userId' => $notJoinedUserId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\n\t\t$this->controller->clearRequests();\n\t\t$this->participantService->updatePermissions($room, $guestParticipant, Attendee::PERMISSIONS_MODIFY_SET, Attendee::PERMISSIONS_CUSTOM);\n\n\t\t$this->assertMessageWasSent($room, [\n\t\t\t'type' => 'participants',\n\t\t\t'participants' => [\n\t\t\t\t'changed' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'permissions' => [],\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t\t'users' => [\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $userSession,\n\t\t\t\t\t\t'participantType' => Participant::USER,\n\t\t\t\t\t\t'participantPermissions' => (Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_LOBBY_IGNORE),\n\t\t\t\t\t\t'userId' => $this->userId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => 0,\n\t\t\t\t\t\t'participantType' => Participant::MODERATOR,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,\n\t\t\t\t\t\t'userId' => $notJoinedUserId,\n\t\t\t\t\t],\n\t\t\t\t\t[\n\t\t\t\t\t\t'inCall' => 0,\n\t\t\t\t\t\t'lastPing' => 0,\n\t\t\t\t\t\t'sessionId' => $guestSession,\n\t\t\t\t\t\t'participantType' => Participant::GUEST,\n\t\t\t\t\t\t'participantPermissions' => Attendee::PERMISSIONS_CUSTOM,\n\t\t\t\t\t],\n\t\t\t\t],\n\t\t\t],\n\t\t]);\n\t}\n}\n", 'message': 'Add a const for magic 0 as well\n\nSigned-off-by: Joas Schilling <ab43a7c9cb5b2380afc4ddf8b3e2583169b39a02@schilljs.com>\n', 'author_name': 'Joachim Bauch', 'lang': 'php'}