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 { protected IURLGenerator&MockObject $urlGenerator; protected ParticipantService $participantService; protected ?CustomBackendNotifier $backendNotifier = null; protected ?Config $config = null; protected ?ISecureRandom $secureRandom = null; protected ?Manager $manager = null; protected ?string $recordingSecret = null; protected ?string $baseUrl = null; public function setUp(): void { parent::setUp(); $config = \OCP\Server::get(IConfig::class); $this->recordingSecret = 'the-recording-secret'; $this->baseUrl = 'https://localhost/recording'; $config->setAppValue('spreed', 'recording_servers', json_encode([ 'secret' => $this->recordingSecret, 'servers' => [ [ 'server' => $this->baseUrl, ], ], ])); $this->secureRandom = \OCP\Server::get(ISecureRandom::class); $this->urlGenerator = $this->createMock(IURLGenerator::class); $appConfig = $this->createMock(IAppConfig::class); $groupManager = $this->createMock(IGroupManager::class); $userManager = $this->createMock(IUserManager::class); $timeFactory = $this->createMock(ITimeFactory::class); $dispatcher = \OCP\Server::get(IEventDispatcher::class); $this->config = new Config($config, $appConfig, $this->createMock(IUserConfig::class), $this->secureRandom, $groupManager, $userManager, $this->urlGenerator, $timeFactory, $dispatcher); $this->recreateBackendNotifier(); $this->participantService = \OCP\Server::get(ParticipantService::class); $dbConnection = \OCP\Server::get(IDBConnection::class); $this->manager = new Manager( $dbConnection, $config, $this->config, \OCP\Server::get(IAppManager::class), \OCP\Server::get(AttendeeMapper::class), \OCP\Server::get(SessionMapper::class), $this->participantService, $this->secureRandom, $this->createMock(IUserManager::class), $groupManager, $this->createMock(CommentsManager::class), $this->createMock(TalkSession::class), $dispatcher, $timeFactory, $this->createMock(IHasher::class), $this->createMock(IL10N::class), $this->createMock(Authenticator::class), ); } public function tearDown(): void { $config = \OCP\Server::get(IConfig::class); $config->deleteAppValue('spreed', 'recording_servers'); parent::tearDown(); } private function recreateBackendNotifier() { $this->backendNotifier = new CustomBackendNotifier( $this->config, $this->createMock(LoggerInterface::class), $this->createMock(IClientService::class), $this->secureRandom, $this->urlGenerator, ); } private function calculateBackendChecksum($data, $random) { if (empty($random) || strlen($random) < 32) { return false; } return hash_hmac('sha256', $random . $data, $this->recordingSecret); } 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['Talk-Recording-Random']; $checksum = $headers['Talk-Recording-Checksum']; $body = $request['params']['body']; $this->assertEquals($this->calculateBackendChecksum($body, $random), $checksum); return $body; } private function assertMessageWasSent(Room $room, array $message): void { $expectedUrl = $this->baseUrl . '/api/v1/room/' . $room->getToken(); $requests = $this->backendNotifier->getRequests(); $requests = array_filter($requests, function ($request) use ($expectedUrl) { return $request['url'] === $expectedUrl; }); $bodies = array_map(function ($request) use ($expectedUrl) { return json_decode($this->validateBackendRequest($expectedUrl, $request), true); }, $requests); $bodies = array_filter($bodies, function (array $body) use ($message) { return $body['type'] === $message['type']; }); $this->assertContainsEquals($message, $bodies, json_encode($bodies, JSON_PRETTY_PRINT)); } public function testStart(): void { $userId = 'testUser'; /** @var IUser&MockObject $testUser */ $testUser = $this->createMock(IUser::class); $testUser->expects($this->any()) ->method('getUID') ->willReturn($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' => $userId, ]]); $participant = $this->participantService->joinRoom($roomService, $room, $testUser, ''); $this->backendNotifier->start($room, Room::RECORDING_VIDEO, 'participant1', $participant); $this->assertMessageWasSent($room, [ 'type' => 'start', 'start' => [ 'status' => Room::RECORDING_VIDEO, 'owner' => 'participant1', 'actor' => [ 'type' => 'users', 'id' => $userId, ], ], ]); } public function testStop(): void { $userId = 'testUser'; /** @var IUser&MockObject $testUser */ $testUser = $this->createMock(IUser::class); $testUser->expects($this->any()) ->method('getUID') ->willReturn($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' => $userId, ]]); $participant = $this->participantService->joinRoom($roomService, $room, $testUser, ''); $this->backendNotifier->stop($room, $participant); $this->assertMessageWasSent($room, [ 'type' => 'stop', 'stop' => [ 'actor' => [ 'type' => 'users', 'id' => $userId, ], ], ]); $this->backendNotifier->stop($room); $this->assertMessageWasSent($room, [ 'type' => 'stop', 'stop' => [ ], ]); } }