UPSTREAM BASELINE: nextcloud/spreed v22.0.12 (без изменений)
Type checking / changes (push) Has been cancelled
Type checking / test (push) Has been cancelled
Type checking / typescript-summary (push) Has been cancelled
Node tests / changes (push) Has been cancelled
Node tests / test (push) Has been cancelled
Node tests / test-summary (push) Has been cancelled

Источник: https://github.com/nextcloud/spreed/archive/refs/tags/v22.0.12.tar.gz
С этого коммита ветка официального Nextcloud Talk отрезана (решение владельца 2026-07-06).
Все дальнейшие изменения — только наши; версии релизов: 22.0.12-f7.N.
This commit is contained in:
2026-07-06 14:07:50 +00:00
commit 01acfa3b40
1716 changed files with 613013 additions and 0 deletions
+79
View File
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Room;
use OCA\Talk\Service\AvatarService;
use OCA\Talk\Service\EmojiService;
use OCA\Talk\Service\RoomService;
use OCP\Files\IAppData;
use OCP\IAvatarManager;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Security\ISecureRandom;
use OCP\Server;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
#[Group('DB')]
class AvatarServiceTest extends TestCase {
protected IAppData&MockObject $appData;
protected IL10N&MockObject $l;
protected IURLGenerator&MockObject $url;
protected ISecureRandom&MockObject $random;
protected RoomService&MockObject $roomService;
protected IAvatarManager&MockObject $avatarManager;
protected EmojiService $emojiService;
protected ?AvatarService $service = null;
public function setUp(): void {
parent::setUp();
$this->appData = $this->createMock(IAppData::class);
$this->l = $this->createMock(IL10N::class);
$this->url = $this->createMock(IURLGenerator::class);
$this->random = $this->createMock(ISecureRandom::class);
$this->roomService = $this->createMock(RoomService::class);
$this->avatarManager = $this->createMock(IAvatarManager::class);
$this->emojiService = Server::get(EmojiService::class);
$this->service = new AvatarService(
$this->appData,
$this->l,
$this->url,
$this->random,
$this->roomService,
$this->avatarManager,
$this->emojiService,
);
}
public static function dataGetAvatarVersion(): array {
return [
['', 'STRING WITH 8 CHARS'],
['1', '1'],
['1.png', '1'],
];
}
#[DataProvider('dataGetAvatarVersion')]
public function testGetAvatarVersion(string $avatar, string $expected): void {
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
$room->method('getAvatar')
->willReturn($avatar);
$actual = $this->service->getAvatarVersion($room);
if ($expected === 'STRING WITH 8 CHARS') {
$this->assertEquals(8, strlen($actual));
} else {
$this->assertEquals($expected, $actual);
}
}
}
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Config;
use OCA\Talk\Manager;
use OCA\Talk\Service\BreakoutRoomService;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RoomService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IL10N;
use OCP\Notification\IManager as INotificationManager;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class BreakoutRoomServiceTest extends TestCase {
protected Config&MockObject $config;
protected Manager&MockObject $manager;
protected RoomService&MockObject $roomService;
protected ParticipantService&MockObject $participantService;
protected ChatManager&MockObject $chatManager;
protected INotificationManager&MockObject $notificationManager;
protected ITimeFactory&MockObject $timeFactory;
protected IEventDispatcher&MockObject $dispatcher;
protected IL10N&MockObject $l;
protected BreakoutRoomService $service;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(Config::class);
$this->manager = $this->createMock(Manager::class);
$this->roomService = $this->createMock(RoomService::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->chatManager = $this->createMock(ChatManager::class);
$this->notificationManager = $this->createMock(INotificationManager::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->dispatcher = $this->createMock(IEventDispatcher::class);
$this->l = $this->createMock(IL10N::class);
$this->service = new BreakoutRoomService(
$this->config,
$this->manager,
$this->roomService,
$this->participantService,
$this->chatManager,
$this->notificationManager,
$this->timeFactory,
$this->dispatcher,
$this->l
);
}
public static function dataParseAttendeeMap(): array {
return [
'Empty string means no map' => ['', 3, [], false],
'Empty array means no map' => ['[]', 3, [], false],
'OK' => [json_encode([1 => 1, 13 => 0, 42 => 2]), 3, [1 => 1, 13 => 0, 42 => 2], false],
'Not an array' => ['"hello"', 3, null, true],
'Room above max' => [json_encode([1 => 0, 13 => 1, 42 => 2]), 2, null, true],
'Room below min' => [json_encode([1 => 0, 13 => -1, 42 => 2]), 3, null, true],
'Room not int' => [json_encode([1 => 0, 13 => 'foo', 42 => 2]), 3, null, true],
'Room null' => [json_encode([1 => 0, 13 => null, 42 => 2]), 3, null, true],
'Attendee not int' => [json_encode([1 => 0, 'foo' => 1, 42 => 2]), 3, null, true],
'Attendee negative' => [json_encode([1 => 0, -13 => 1, 42 => 2]), 3, null, true],
'Attendee zero' => [json_encode([1 => 0, 0 => 1, 42 => 2]), 3, null, true],
];
}
#[DataProvider('dataParseAttendeeMap')]
public function testParseAttendeeMap(string $json, int $max, ?array $expected, bool $throws): void {
if ($throws) {
$this->expectException(\InvalidArgumentException::class);
}
$actual = self::invokePrivate($this->service, 'parseAttendeeMap', [$json, $max]);
$this->assertEquals($expected, $actual);
}
}
@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Service\CertificateService;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class CertificateServiceTest extends TestCase {
protected CertificateService $service;
public function setUp(): void {
parent::setUp();
$logger = $this->createMock(LoggerInterface::class);
$this->service = new CertificateService($logger);
}
public function testGetParsedTlsHost(): void {
$actual = $this->service->getParsedTlsHost('domain.com');
$this->assertEquals($actual, 'domain.com');
$actual = $this->service->getParsedTlsHost('subdomain.domain.com');
$this->assertEquals($actual, 'subdomain.domain.com');
$actual = $this->service->getParsedTlsHost('https://domain.com');
$this->assertEquals($actual, 'domain.com');
$actual = $this->service->getParsedTlsHost('https://domain.com:1234');
$this->assertEquals($actual, 'domain.com:1234');
$actual = $this->service->getParsedTlsHost('https://domain.com:1234/path/1/');
$this->assertEquals($actual, 'domain.com:1234');
$actual = $this->service->getParsedTlsHost('http://domain.com:1234/path/1/');
$this->assertNull($actual);
}
}
@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Exceptions\UnauthorizedException;
use OCA\Talk\Service\ChecksumVerificationService;
use PHPUnit\Framework\Attributes\DataProvider;
use Test\TestCase;
class ChecksumVerificationServiceTest extends TestCase {
protected ChecksumVerificationService $service;
public function setUp(): void {
parent::setUp();
$this->service = new ChecksumVerificationService();
}
public static function dataValidateRequest(): array {
$validRandom = md5(random_bytes(15));
$fakeData = json_encode(['fake' => 'data']);
$validSecret = 'valid secret';
$validChecksum = hash_hmac('sha256', $validRandom . $fakeData, $validSecret);
return [
['', '', '', '', '', false],
['1234', '', '', '', 'Invalid random provided', false],
[str_repeat('1', 32), '', '', '', 'Invalid checksum provided', false],
[str_repeat('1', 32), 'fake', '', '', 'No secret provided', false],
[str_repeat('1', 32), 'fake', 'invalid', '', 'Invalid HMAC provided', false],
[$validRandom, $validChecksum, $validSecret, $fakeData, '', true],
];
}
#[DataProvider('dataValidateRequest')]
public function testValidateRequest(string $random, string $checksum, string $secret, string $token, string $exceptionMessage, bool $expectedReturn): void {
if ($exceptionMessage) {
$this->expectException(UnauthorizedException::class);
$this->expectExceptionMessage($exceptionMessage);
}
$actual = $this->service->validateRequest($random, $checksum, $secret, $token);
if (!$exceptionMessage) {
$this->assertEquals($expectedReturn, $actual);
}
}
}
+42
View File
@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Service\EmojiService;
use OCP\IEmojiHelper;
use OCP\Server;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use Test\TestCase;
#[Group('DB')]
class EmojiServiceTest extends TestCase {
protected ?EmojiService $service = null;
public function setUp(): void {
parent::setUp();
$this->service = new EmojiService(
Server::get(IEmojiHelper::class),
);
}
public static function dataGetFirstCombinedEmoji(): array {
return [
['👋 Hello', '👋'],
['Only leading emojis 🚀', ''],
['👩🏽‍💻👩🏻‍💻👨🏿‍💻 Only one, but with all attributes', '👩🏽‍💻'],
];
}
#[DataProvider('dataGetFirstCombinedEmoji')]
public function testGetFirstCombinedEmoji(string $roomName, string $avatarEmoji): void {
$this->assertSame($avatarEmoji, self::invokePrivate($this->service, 'getFirstCombinedEmoji', [$roomName]));
}
}
@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Config;
use OCA\Talk\Federation\BackendNotifier;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\AttendeeMapper;
use OCA\Talk\Model\Session;
use OCA\Talk\Model\SessionMapper;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\MembershipService;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\SessionService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Federation\ICloudIdManager;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;
use OCP\UserStatus\IManager;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
#[Group('DB')]
class ParticipantServiceTest extends TestCase {
protected IConfig&MockObject $serverConfig;
protected Config&MockObject $talkConfig;
protected ?AttendeeMapper $attendeeMapper = null;
protected ?SessionMapper $sessionMapper = null;
protected SessionService&MockObject $sessionService;
protected ISecureRandom&MockObject $secureRandom;
protected IEventDispatcher&MockObject $dispatcher;
protected IUserManager&MockObject $userManager;
protected ICloudIdManager&MockObject $cloudIdManager;
protected IGroupManager&MockObject $groupManager;
protected MembershipService&MockObject $membershipService;
protected BackendNotifier&MockObject $federationBackendNotifier;
protected ITimeFactory&MockObject $time;
protected ICacheFactory&MockObject $cacheFactory;
protected IManager&MockObject $userStatusManager;
private ?ParticipantService $service = null;
protected LoggerInterface&MockObject $logger;
public function setUp(): void {
parent::setUp();
$this->serverConfig = $this->createMock(IConfig::class);
$this->talkConfig = $this->createMock(Config::class);
$this->attendeeMapper = new AttendeeMapper(\OCP\Server::get(IDBConnection::class));
$this->sessionMapper = new SessionMapper(\OCP\Server::get(IDBConnection::class));
$this->sessionService = $this->createMock(SessionService::class);
$this->secureRandom = $this->createMock(ISecureRandom::class);
$this->dispatcher = $this->createMock(IEventDispatcher::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->membershipService = $this->createMock(MembershipService::class);
$this->federationBackendNotifier = $this->createMock(BackendNotifier::class);
$this->time = $this->createMock(ITimeFactory::class);
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->userStatusManager = $this->createMock(IManager::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new ParticipantService(
$this->serverConfig,
$this->talkConfig,
$this->attendeeMapper,
$this->sessionMapper,
$this->sessionService,
$this->secureRandom,
\OCP\Server::get(IDBConnection::class),
$this->dispatcher,
$this->userManager,
$this->cloudIdManager,
$this->groupManager,
$this->membershipService,
$this->federationBackendNotifier,
$this->time,
$this->cacheFactory,
$this->userStatusManager,
$this->logger
);
}
public function tearDown(): void {
try {
$attendee = $this->attendeeMapper->findByActor(123456789, Attendee::ACTOR_USERS, 'test');
$this->sessionMapper->deleteByAttendeeId($attendee->getId());
$this->attendeeMapper->delete($attendee);
} catch (DoesNotExistException $exception) {
}
parent::tearDown();
}
public function testGetParticipantsByNotificationLevel(): void {
$attendee = new Attendee();
$attendee->setActorType(Attendee::ACTOR_USERS);
$attendee->setActorId('test');
$attendee->setRoomId(123456789);
$attendee->setNotificationLevel(Participant::NOTIFY_MENTION);
$this->attendeeMapper->insert($attendee);
$session1 = new Session();
$session1->setAttendeeId($attendee->getId());
$session1->setSessionId(self::getUniqueID('session1'));
$this->sessionMapper->insert($session1);
$session2 = new Session();
$session2->setAttendeeId($attendee->getId());
$session2->setSessionId(self::getUniqueID('session2'));
$this->sessionMapper->insert($session2);
$room = $this->createMock(Room::class);
$room->method('getId')
->willReturn(123456789);
$participants = $this->service->getParticipantsByNotificationLevel($room, Participant::NOTIFY_MENTION);
self::assertCount(1, $participants);
}
}
@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Exceptions\InvalidRoomException;
use OCA\Talk\Model\ProxyCacheMessage;
use OCA\Talk\Model\ProxyCacheMessageMapper;
use OCA\Talk\Room;
use OCA\Talk\Service\ProxyCacheMessageService;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IDBConnection;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
#[Group('DB')]
class ProxyCacheMessageServiceTest extends TestCase {
protected LoggerInterface&MockObject $logger;
protected ITimeFactory&MockObject $timeFactory;
protected ?ProxyCacheMessageMapper $mapper = null;
protected ?ProxyCacheMessageService $service = null;
public function setUp(): void {
parent::setUp();
$this->mapper = new ProxyCacheMessageMapper(\OCP\Server::get(IDBConnection::class));
$this->logger = $this->createMock(LoggerInterface::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->service = new ProxyCacheMessageService(
$this->mapper,
$this->logger,
$this->timeFactory,
);
$this->clearMessages();
}
public function tearDown(): void {
$this->clearMessages();
parent::tearDown();
}
protected function clearMessages(): void {
$query = \OCP\Server::get(IDBConnection::class)->getQueryBuilder();
$query->delete('talk_proxy_messages')
->where($query->expr()->eq('remote_server_url', $query->createNamedParameter('phpunittests')));
$query->executeStatement();
}
public static function dataDeleteExpiredMessages(): array {
return [
[1234, 12345, true],
[1234567, 12345, false],
[null, 12345, false],
];
}
#[DataProvider('dataDeleteExpiredMessages')]
public function testDeleteExpiredMessages(?int $messageTime, int $currentTime, bool $expired): void {
$room = $this->createMock(Room::class);
$room->method('isFederatedConversation')
->willReturn(true);
$m1 = new ProxyCacheMessage();
$m1->setLocalToken('local_token');
$m1->setRemoteServerUrl('phpunittests');
$m1->setRemoteToken('remote_token');
$m1->setRemoteMessageId(12345);
$m1->setActorType('actor_type');
$m1->setActorId('actor_id');
$m1->setMessageType('message_type');
if ($messageTime === null) {
$m1->setExpirationDatetime($messageTime);
} else {
$m1->setExpirationDatetime(new \DateTime('@' . $messageTime));
}
$this->mapper->insert($m1);
$this->mapper->findById($room, $m1->getId());
$this->timeFactory->method('getDateTime')
->willReturn(new \DateTime('@' . $currentTime));
$this->service->deleteExpiredMessages();
if ($expired) {
$this->expectException(DoesNotExistException::class);
}
$actual = $this->mapper->findById($room, $m1->getId());
if (!$expired) {
$this->assertEquals($m1->getId(), $actual->getId());
}
}
public function testFindByIdThrows(): void {
$this->expectException(InvalidRoomException::class);
$room = $this->createMock(Room::class);
$room->method('isFederatedConversation')
->willReturn(false);
$this->mapper->findById($room, 42);
}
}
+163
View File
@@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2022 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Service;
/**
* Overwrite is_uploaded_file in the OCA\Talk\Service namespace
* to allow proper unit testing of the postAvatar call.
*/
function is_uploaded_file($filename) {
return file_exists($filename);
}
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Config;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Recording\BackendNotifier;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RecordingService;
use OCA\Talk\Service\RoomService;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder;
use OCP\IConfig;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Notification\IManager;
use OCP\Share\IManager as ShareManager;
use OCP\TaskProcessing\IManager as ITaskProcessingManager;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class RecordingServiceTest extends TestCase {
private IMimeTypeDetector $mimeTypeDetector;
protected ParticipantService&MockObject $participantService;
protected IRootFolder&MockObject $rootFolder;
protected Config&MockObject $config;
protected IConfig&MockObject $serverConfig;
protected IAppConfig&MockObject $appConfig;
protected IManager&MockObject $notificationManager;
protected Manager&MockObject $roomManager;
protected ITimeFactory&MockObject $timeFactory;
protected RoomService&MockObject $roomService;
protected ShareManager&MockObject $shareManager;
protected ChatManager&MockObject $chatManager;
protected LoggerInterface&MockObject $logger;
protected BackendNotifier&MockObject $backendNotifier;
protected ITaskProcessingManager&MockObject $taskProcessingManager;
protected IFactory&MockObject $l10nFactory;
protected IUserManager&MockObject $userManager;
protected RecordingService $recordingService;
public function setUp(): void {
parent::setUp();
$this->mimeTypeDetector = \OCP\Server::get(IMimeTypeDetector::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->rootFolder = $this->createMock(IRootFolder::class);
$this->notificationManager = $this->createMock(IManager::class);
$this->roomManager = $this->createMock(Manager::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->config = $this->createMock(Config::class);
$this->serverConfig = $this->createMock(IConfig::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->roomService = $this->createMock(RoomService::class);
$this->shareManager = $this->createMock(ShareManager::class);
$this->chatManager = $this->createMock(ChatManager::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->backendNotifier = $this->createMock(BackendNotifier::class);
$this->taskProcessingManager = $this->createMock(ITaskProcessingManager::class);
$this->l10nFactory = $this->createMock(IFactory::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->recordingService = new RecordingService(
$this->mimeTypeDetector,
$this->participantService,
$this->rootFolder,
$this->notificationManager,
$this->roomManager,
$this->timeFactory,
$this->config,
$this->serverConfig,
$this->appConfig,
$this->roomService,
$this->shareManager,
$this->chatManager,
$this->logger,
$this->backendNotifier,
$this->taskProcessingManager,
$this->l10nFactory,
$this->userManager,
);
}
public static function dataValidateFileFormat(): array {
return [
# file_invalid_path
['', '', 'file_invalid_path'],
# file_mimetype
['', realpath(__DIR__ . '/../../../img/app.svg'), 'file_mimetype'],
['name.ogg', realpath(__DIR__ . '/../../../img/app.svg'), 'file_mimetype'],
# file_extension
['', realpath(__DIR__ . '/../../../img/join_call.ogg'), 'file_extension'],
['name', realpath(__DIR__ . '/../../../img/join_call.ogg'), 'file_extension'],
['name.mp3', realpath(__DIR__ . '/../../../img/join_call.ogg'), 'file_extension'],
# Success
['name.ogg', realpath(__DIR__ . '/../../../img/join_call.ogg'), ''],
];
}
#[DataProvider('dataValidateFileFormat')]
public function testValidateFileFormat(string $fileName, string $fileRealPath, string $exceptionMessage): void {
if ($exceptionMessage) {
$this->expectExceptionMessage($exceptionMessage);
} else {
$this->expectNotToPerformAssertions();
}
$this->recordingService->validateFileFormat($fileName, $fileRealPath);
}
public static function dataGetResourceFromFileArray(): array {
$fileWithContent = tempnam(sys_get_temp_dir(), 'txt');
file_put_contents($fileWithContent, 'bla');
return [
[['error' => 1, 'tmp_name' => ''], '', 'invalid_file'],
[['error' => 1, 'tmp_name' => 'a'], '', 'invalid_file'],
# Empty file
[['error' => 0, 'tmp_name' => tempnam(sys_get_temp_dir(), 'txt')], '', 'empty_file'],
# file with content
[['error' => 0, 'tmp_name' => $fileWithContent], 'bla', ''],
];
}
#[DataProvider('dataGetResourceFromFileArray')]
public function testGetResourceFromFileArray(array $file, string $expected, string $exceptionMessage): void {
if ($exceptionMessage) {
$this->expectExceptionMessage($exceptionMessage);
}
$room = $this->createMock(Room::class);
$attendee = Attendee::fromRow([
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'participant1',
]);
$participant = new Participant($room, $attendee, null);
$actual = stream_get_contents($this->recordingService->getResourceFromFileArray($file, $room, $participant));
$this->assertEquals($expected, $actual);
}
}
+390
View File
@@ -0,0 +1,390 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2020 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use InvalidArgumentException;
use OC\EventDispatcher\EventDispatcher;
use OCA\Talk\Config;
use OCA\Talk\Events\RoomPasswordVerifyEvent;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\BreakoutRoom;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\EmojiService;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RecordingService;
use OCA\Talk\Service\RoomService;
use OCA\Talk\Webinary;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\BackgroundJob\IJobList;
use OCP\Calendar\IManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\IUser;
use OCP\Security\IHasher;
use OCP\Server;
use OCP\Share\IManager as IShareManager;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
#[Group('DB')]
class RoomServiceTest extends TestCase {
protected Manager&MockObject $manager;
protected ParticipantService&MockObject $participantService;
protected ITimeFactory&MockObject $timeFactory;
protected IShareManager&MockObject $shareManager;
protected Config&MockObject $config;
protected IHasher&MockObject $hasher;
protected IEventDispatcher&MockObject $dispatcher;
protected IJobList&MockObject $jobList;
protected LoggerInterface&MockObject $logger;
protected IL10N&MockObject $l10n;
protected IManager $calendarManager;
protected EmojiService $emojiService;
protected ?RoomService $service = null;
public function setUp(): void {
parent::setUp();
$this->manager = $this->createMock(Manager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->shareManager = $this->createMock(IShareManager::class);
$this->config = $this->createMock(Config::class);
$this->hasher = $this->createMock(IHasher::class);
$this->dispatcher = $this->createMock(IEventDispatcher::class);
$this->jobList = $this->createMock(IJobList::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->l10n = $this->createMock(IL10N::class);
$this->emojiService = Server::get(EmojiService::class);
$this->calendarManager = $this->createMock(IManager::class);
$this->service = new RoomService(
$this->manager,
$this->participantService,
\OCP\Server::get(IDBConnection::class),
$this->timeFactory,
$this->shareManager,
$this->config,
$this->hasher,
$this->dispatcher,
$this->jobList,
$this->emojiService,
$this->logger,
$this->l10n,
$this->calendarManager,
);
}
public function testCreateOneToOneConversationWithSameUser(): void {
$user = $this->createMock(IUser::class);
$user->method('getUID')
->willReturn('uid');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('invite');
$this->service->createOneToOneConversation($user, $user);
}
public function testCreateOneToOneConversationWithNotCurrentUserCanEnumerateTargetUser(): void {
$user1 = $this->createMock(IUser::class);
$user1->method('getUID')
->willReturn('uid1');
$user2 = $this->createMock(IUser::class);
$user2->method('getUID')
->willReturn('uid2');
$this->expectException(RoomNotFoundException::class);
$this->shareManager
->expects($this->once())
->method('currentUserCanEnumerateTargetUser')
->willReturn(false);
$this->manager
->method('getOne2OneRoom')
->willThrowException(new RoomNotFoundException());
$this->service->createOneToOneConversation($user1, $user2);
}
public function testCreateOneToOneConversationAlreadyExists(): void {
$user1 = $this->createMock(IUser::class);
$user1->method('getUID')
->willReturn('uid1');
$user2 = $this->createMock(IUser::class);
$user2->method('getUID')
->willReturn('uid2');
$room = $this->createMock(Room::class);
$this->participantService->expects($this->once())
->method('ensureOneToOneRoomIsFilled')
->with($room);
$this->manager->expects($this->once())
->method('getOne2OneRoom')
->with('uid1', 'uid2')
->willReturn($room);
$this->assertSame($room, $this->service->createOneToOneConversation($user1, $user2));
}
public function testCreateOneToOneConversationCreated(): void {
$user1 = $this->createMock(IUser::class);
$user1->method('getUID')
->willReturn('uid1');
$user1->method('getDisplayName')
->willReturn('display-1');
$user2 = $this->createMock(IUser::class);
$user2->method('getUID')
->willReturn('uid2');
$user2->method('getDisplayName')
->willReturn('display-2');
$this->shareManager
->expects($this->once())
->method('currentUserCanEnumerateTargetUser')
->willReturn(true);
$room = $this->createMock(Room::class);
$this->participantService->expects($this->once())
->method('addUsers')
->with($room, [[
'actorType' => 'users',
'actorId' => 'uid1',
'displayName' => 'display-1',
'participantType' => Participant::OWNER,
]]);
$this->participantService->expects($this->never())
->method('ensureOneToOneRoomIsFilled')
->with($room);
$this->manager->expects($this->once())
->method('getOne2OneRoom')
->with('uid1', 'uid2')
->willThrowException(new RoomNotFoundException());
$this->manager->expects($this->once())
->method('createRoom')
->with(Room::TYPE_ONE_TO_ONE)
->willReturn($room);
$this->assertSame($room, $this->service->createOneToOneConversation($user1, $user2));
}
public static function dataCreateConversationInvalidNames(): array {
return [
[''],
[' '],
[str_repeat('a', 256)],
// Isn't a multibyte emoji
[str_repeat('😃', 256)],
// This is a multibyte emoji and need 2 chars in database
// 256 / 2 = 128
[str_repeat('‍💻', 128)],
];
}
#[DataProvider('dataCreateConversationInvalidNames')]
public function testCreateConversationInvalidNames(string $name): void {
$this->manager->expects($this->never())
->method('createRoom');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('name');
$this->service->createConversation(Room::TYPE_GROUP, $name);
}
public static function dataCreateConversationInvalidTypes(): array {
return [
[Room::TYPE_ONE_TO_ONE],
[Room::TYPE_UNKNOWN],
[Room::TYPE_ONE_TO_ONE_FORMER],
[7],
];
}
#[DataProvider('dataCreateConversationInvalidTypes')]
public function testCreateConversationInvalidTypes(int $type): void {
$this->manager->expects($this->never())
->method('createRoom');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('type');
$this->service->createConversation($type, 'abc');
}
public static function dataCreateConversationInvalidObjects(): array {
return [
[str_repeat('a', 65), 'a', 'object-type'],
['a', str_repeat('a', 65), 'object-id'],
['a', '', 'object'],
['', 'b', 'object'],
];
}
#[DataProvider('dataCreateConversationInvalidObjects')]
public function testCreateConversationInvalidObjects(string $type, string $id, string $exception): void {
$this->manager->expects($this->never())
->method('createRoom');
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage($exception);
$this->service->createConversation(Room::TYPE_PUBLIC, 'a', null, $type, $id);
}
public static function dataCreateConversation(): array {
return [
[Room::TYPE_GROUP, 'Group conversation', 'admin', '', '', ''],
[Room::TYPE_PUBLIC, 'Public conversation', '', 'file', '123456', ''],
[Room::TYPE_PUBLIC, 'Public conversation', '', 'file', '123456', 'AGoodPassword123?'],
[Room::TYPE_CHANGELOG, 'Talk updates ✅', 'test1', '', '', ''],
[Room::TYPE_GROUP, 'Let\'s get started!', 'test1', 'sample', 'test1', ''],
];
}
#[DataProvider('dataCreateConversation')]
public function testCreateConversation(int $type, string $name, string $ownerId, string $objectType, string $objectId, string $password): void {
$room = $this->createMock(Room::class);
if ($ownerId !== '') {
$owner = $this->createMock(IUser::class);
$owner->method('getUID')
->willReturn($ownerId);
$owner->method('getDisplayName')
->willReturn($ownerId . '-display');
$this->participantService->expects($this->once())
->method('addUsers')
->with($room, [[
'actorType' => 'users',
'actorId' => $ownerId,
'displayName' => $ownerId . '-display',
'participantType' => Participant::OWNER,
]]);
} else {
$owner = null;
$this->participantService->expects($this->never())
->method('addUsers');
}
if ($password !== '') {
$this->hasher->expects(self::once())
->method('hash')
->willReturn($password);
}
$this->manager->expects($this->once())
->method('createRoom')
->with($type, $name, $objectType, $objectId, $password)
->willReturn($room);
$this->assertSame($room, $this->service->createConversation($type, $name, $owner, $objectType, $objectId, $password));
}
public static function dataPrepareConversationName(): array {
return [
['', ''],
[' ', ''],
['A ', 'A'],
[' B', 'B'],
[' C ', 'C'],
['A' . str_repeat(' ', 100) . 'B', 'A'],
['A' . str_repeat(' ', 32) . 'B', 'A' . str_repeat(' ', 32) . 'B'],
['Лорем ипсум долор сит амет, но антиопам алияуандо витуперата еам, мел те цонгуе хомеро адолесценс.', 'Лорем ипсум долор сит амет, но антиопам алияуандо витуперата еам'],
];
}
#[DataProvider('dataPrepareConversationName')]
public function testPrepareConversationName(string $input, string $expected): void {
$this->assertSame($expected, $this->service->prepareConversationName($input));
}
public function testVerifyPassword(): void {
$dispatcher = new EventDispatcher(
new \Symfony\Component\EventDispatcher\EventDispatcher(),
\OC::$server,
$this->createMock(LoggerInterface::class)
);
$dispatcher->addListener(RoomPasswordVerifyEvent::class, static function (RoomPasswordVerifyEvent $event): void {
$password = $event->getPassword();
if ($password === '1234') {
$event->setIsPasswordValid(true);
$event->setRedirectUrl('');
} else {
$event->setIsPasswordValid(false);
$event->setRedirectUrl('https://test');
}
});
$service = new RoomService(
$this->manager,
$this->participantService,
\OCP\Server::get(IDBConnection::class),
$this->timeFactory,
$this->shareManager,
$this->config,
$this->hasher,
$dispatcher,
$this->jobList,
$this->emojiService,
$this->logger,
$this->l10n,
$this->calendarManager,
);
$room = new Room(
$this->createMock(Manager::class),
$this->createMock(IDBConnection::class),
$dispatcher,
$this->createMock(ITimeFactory::class),
1,
Room::TYPE_PUBLIC,
Room::READ_WRITE,
Room::LISTABLE_NONE,
0,
Webinary::LOBBY_NONE,
Webinary::SIP_DISABLED,
null,
'foobar',
'Test',
'description',
'passy',
'',
'',
'',
Attendee::PERMISSIONS_DEFAULT,
Attendee::PERMISSIONS_DEFAULT,
Participant::FLAG_DISCONNECTED,
null,
null,
0,
null,
null,
'',
'',
BreakoutRoom::MODE_NOT_CONFIGURED,
BreakoutRoom::STATUS_STOPPED,
Room::RECORDING_NONE,
RecordingService::CONSENT_REQUIRED_NO,
Room::HAS_FEDERATION_NONE,
Room::MENTION_PERMISSIONS_EVERYONE,
'',
);
$verificationResult = $service->verifyPassword($room, '1234');
$this->assertSame($verificationResult, ['result' => true, 'url' => '']);
$verificationResult = $service->verifyPassword($room, '4321');
$this->assertSame($verificationResult, ['result' => false, 'url' => 'https://test']);
$this->assertSame('passy', $room->getPassword());
}
}
+105
View File
@@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Service\SIPDialOutService;
use OCA\Talk\Signaling\BackendNotifier;
use OCA\Talk\Signaling\Responses\DialOut;
use OCA\Talk\Signaling\Responses\DialOutError;
use OCA\Talk\Signaling\Responses\Response;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class SIPDialOutServiceTest extends TestCase {
protected BackendNotifier&MockObject $backendNotifier;
protected LoggerInterface&MockObject $logger;
protected ?SIPDialOutService $service = null;
public function setUp(): void {
parent::setUp();
$this->backendNotifier = $this->createMock(BackendNotifier::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->service = new SIPDialOutService(
$this->backendNotifier,
$this->logger,
);
}
public function testValidateDialOutResponseSuccess(): void {
$data = <<<JSON
{
"type": "dialout",
"dialout": {
"callid": "the-call-id"
}
}
JSON;
/** @var Response $response */
$response = self::invokePrivate($this->service, 'validateDialOutResponse', [$data]);
$this->assertInstanceOf(Response::class, $response);
$this->assertInstanceOf(DialOut::class, $response->dialOut);
$this->assertSame('the-call-id', $response->dialOut->callId);
$this->assertNull($response->dialOut->error);
}
public function testValidateDialOutResponseError(): void {
$data = <<<JSON
{
"type": "dialout",
"dialout": {
"error": {
"code": "error-code",
"message": "Human readable error."
}
}
}
JSON;
/** @var Response $response */
$response = self::invokePrivate($this->service, 'validateDialOutResponse', [$data]);
$this->assertInstanceOf(Response::class, $response);
$this->assertInstanceOf(DialOut::class, $response->dialOut);
$this->assertInstanceOf(DialOutError::class, $response->dialOut->error);
$this->assertNull($response->dialOut->callId);
$this->assertSame('error-code', $response->dialOut->error->code);
$this->assertSame('Human readable error.', $response->dialOut->error->message);
}
public function testValidateDialOutResponseErrorWithDetails(): void {
$data = <<<JSON
{
"type": "dialout",
"dialout": {
"error": {
"code": "error-code",
"message": "Human readable error.",
"details": {
"attendeeId": 32
}
}
}
}
JSON;
/** @var Response $response */
$response = self::invokePrivate($this->service, 'validateDialOutResponse', [$data]);
$this->assertInstanceOf(Response::class, $response);
$this->assertInstanceOf(DialOut::class, $response->dialOut);
$this->assertInstanceOf(DialOutError::class, $response->dialOut->error);
$this->assertNull($response->dialOut->callId);
$this->assertSame('error-code', $response->dialOut->error->code);
$this->assertSame('Human readable error.', $response->dialOut->error->message);
}
}
+199
View File
@@ -0,0 +1,199 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Service;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\SessionMapper;
use OCA\Talk\Service\SessionService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IDBConnection;
use OCP\Security\ISecureRandom;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
#[Group('DB')]
class SessionServiceTest extends TestCase {
protected ?SessionMapper $sessionMapper = null;
protected ISecureRandom&MockObject $secureRandom;
protected ITimeFactory&MockObject $timeFactory;
private ?SessionService $service = null;
private const RANDOM_254 = '123456789abcdef0123456789abcdef1123456789abcdef2123456789abcdef3123456789abcdef4123456789abcdef5123456789abcdef6123456789abcdef7123456789abcdef8123456789abcdef9123456789abcdefa123456789abcdefb123456789abcdefc123456789abcdefd123456789abcdefe123456789abcde';
private array $attendeeIds = [];
public function setUp(): void {
parent::setUp();
$this->sessionMapper = \OCP\Server::get(SessionMapper::class);
$this->secureRandom = $this->createMock(ISecureRandom::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->service = new SessionService(
$this->sessionMapper,
\OCP\Server::get(IDBConnection::class),
$this->secureRandom,
$this->timeFactory,
);
}
public function tearDown(): void {
foreach ($this->attendeeIds as $attendeeId) {
try {
$this->sessionMapper->deleteByAttendeeId($attendeeId);
} catch (DoesNotExistException $exception) {
}
}
parent::tearDown();
}
public function testCreateSessionForAttendee() {
$attendee = new Attendee();
$attendee->setId(42);
$attendee->setActorType(Attendee::ACTOR_USERS);
$attendee->setActorId('test');
$this->attendeeIds[] = $attendee->getId();
$random = self::RANDOM_254 . 'x';
$this->secureRandom->expects($this->once())
->method('generate')
->with(255)
->willReturn($random);
$session = $this->service->createSessionForAttendee($attendee);
self::assertEquals($random, $session->getSessionId());
}
public function testCreateSessionForAttendeeWithDuplicatedSessionId() {
$attendee1 = new Attendee();
$attendee1->setId(42);
$attendee1->setActorType(Attendee::ACTOR_USERS);
$attendee1->setActorId('test1');
$this->attendeeIds[] = $attendee1->getId();
$attendee2 = new Attendee();
$attendee2->setId(108);
$attendee2->setActorType(Attendee::ACTOR_USERS);
$attendee2->setActorId('test2');
$this->attendeeIds[] = $attendee2->getId();
$random1 = self::RANDOM_254 . 'x';
$random2 = self::RANDOM_254 . 'y';
$this->secureRandom->expects($this->exactly(3))
->method('generate')
->with(255)
->willReturn(
$random1,
$random1,
$random2,
);
$session1 = $this->service->createSessionForAttendee($attendee1);
$session2 = $this->service->createSessionForAttendee($attendee2);
self::assertEquals($random1, $session1->getSessionId());
self::assertEquals($random2, $session2->getSessionId());
}
public function testCreateSessionForAttendeeWithoutId() {
$attendee = new Attendee();
$attendee->setActorType(Attendee::ACTOR_USERS);
$attendee->setActorId('test');
$random = self::RANDOM_254 . 'x';
$this->secureRandom->expects($this->once())
->method('generate')
->with(255)
->willReturn($random);
$this->expectException(\OC\DB\Exceptions\DbalException::class);
$session = $this->service->createSessionForAttendee($attendee);
}
public function testCreateSessionForAttendeeWithInvitedCloudId() {
$attendee = new Attendee();
$attendee->setId(42);
$attendee->setActorType(Attendee::ACTOR_USERS);
$attendee->setActorId('test');
$this->attendeeIds[] = $attendee->getId();
$random = self::RANDOM_254 . 'x';
$this->secureRandom->expects($this->once())
->method('generate')
->with(255)
->willReturn($random);
$cloudId = 'user@server.com';
$attendee->setInvitedCloudId($cloudId);
$session = $this->service->createSessionForAttendee($attendee);
self::assertEquals($random . '#' . $cloudId, $session->getSessionId());
}
public function testExtendSessionIdWithMaximumLengthCloudId(): void {
$attendee = new Attendee();
$attendee->setId(42);
$attendee->setActorType(Attendee::ACTOR_USERS);
$attendee->setActorId('test');
$this->attendeeIds[] = $attendee->getId();
$random = self::RANDOM_254 . 'x';
$this->secureRandom->expects($this->once())
->method('generate')
->with(255)
->willReturn($random);
// User ids are 64 characters long at most; total cloud id length needs
// to leave room for the '#' joining the ids.
$cloudId = 'user123456789abcdef0123456789abcdef1123456789abcdef2123456789abc@server123456789abcdef0123456789abcdef1123456789abcdef2123456789abcdef3123456789abcdef4123456789abcdef5123456789abcdef6123456789abcdef7123456789abcdef8123456789abcdef9123456789abcdefa12345.com';
$attendee->setInvitedCloudId($cloudId);
$session = $this->service->createSessionForAttendee($attendee);
self::assertEquals(256, strlen($cloudId));
self::assertEquals(512, strlen($session->getSessionId()));
self::assertEquals($random . '#' . $cloudId, $session->getSessionId());
}
public function testExtendSessionIdWithTooLongCloudId(): void {
$attendee = new Attendee();
$attendee->setId(42);
$attendee->setActorType(Attendee::ACTOR_USERS);
$attendee->setActorId('test');
$this->attendeeIds[] = $attendee->getId();
$random = self::RANDOM_254 . 'x';
$this->secureRandom->expects($this->once())
->method('generate')
->with(255)
->willReturn($random);
// User ids are 64 characters long at most; total cloud id length needs
// to leave room for the '#' joining the ids.
$cloudId = 'user123456789abcdef0123456789abcdef1123456789abcdef2123456789abc@server123456789abcdef0123456789abcdef1123456789abcdef2123456789abcdef3123456789abcdef4123456789abcdef5123456789abcdef6123456789abcdef7123456789abcdef8123456789abcdef9123456789abcdefa123456.com';
$trimmedCloudId = 'user123456789abcdef0123456789abcdef1123456789abcdef2123456789abc@server123456789abcdef0123456789abcdef1123456789abcdef2123456789abcdef3123456789abcdef4123456789abcdef5123456789abcdef6123456789abcdef7123456789abcdef8123456789abcdef9123456789abcdefa123456.co';
$attendee->setInvitedCloudId($cloudId);
$session = $this->service->createSessionForAttendee($attendee);
self::assertEquals(257, strlen($cloudId));
self::assertEquals(512, strlen($session->getSessionId()));
self::assertEquals($random . '#' . $trimmedCloudId, $session->getSessionId());
}
}