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
+235
View File
@@ -0,0 +1,235 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Activity\Provider;
use OCA\Talk\Activity\Provider\Base;
use OCA\Talk\Config;
use OCA\Talk\Manager;
use OCA\Talk\Room;
use OCA\Talk\Service\AvatarService;
use OCA\Talk\Service\ParticipantService;
use OCP\Activity\Exceptions\UnknownActivityException;
use OCP\Activity\IEvent;
use OCP\Activity\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
/**
* Class BaseTest
*
* @package OCA\Talk\Tests\php\Activity
*/
class BaseTest extends TestCase {
protected IFactory&MockObject $l10nFactory;
protected IURLGenerator&MockObject $url;
protected Config&MockObject $config;
protected IManager&MockObject $activityManager;
protected IUserManager&MockObject $userManager;
protected ICloudIdManager&MockObject $cloudIdManager;
protected ParticipantService&MockObject $participantService;
protected AvatarService&MockObject $avatarService;
protected Manager&MockObject $manager;
public function setUp(): void {
parent::setUp();
$this->l10nFactory = $this->createMock(IFactory::class);
$this->url = $this->createMock(IURLGenerator::class);
$this->config = $this->createMock(Config::class);
$this->activityManager = $this->createMock(IManager::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->avatarService = $this->createMock(AvatarService::class);
$this->manager = $this->createMock(Manager::class);
}
/**
* @param string[] $methods
*/
protected function getProvider(array $methods = []): Base&MockObject {
$methods[] = 'parse';
return $this->getMockBuilder(Base::class)
->setConstructorArgs([
$this->l10nFactory,
$this->url,
$this->config,
$this->activityManager,
$this->userManager,
$this->cloudIdManager,
$this->participantService,
$this->avatarService,
$this->manager,
])
->onlyMethods($methods)
->getMock();
}
public static function dataPreParse(): array {
return [
['other', false, true, true],
['spreed', false, true, true],
['spreed', true, true, true],
['spreed', true, false, false],
];
}
#[DataProvider('dataPreParse')]
public function testPreParse(string $appId, bool $hasUser, bool $disabledForUser, bool $willThrowException): void {
$user = $hasUser ? $this->createMock(IUser::class) : null;
/** @var IEvent&MockObject $event */
$event = $this->createMock(IEvent::class);
$event->expects($this->once())
->method('getApp')
->willReturn($appId);
if ($willThrowException) {
$this->expectException(UnknownActivityException::class);
}
$event->expects($this->exactly($willThrowException ? 0 : 1))
->method('setIcon')
->willReturnSelf();
if ($user) {
$this->config
->method('isDisabledForUser')
->with($user)
->willReturn($disabledForUser);
$this->userManager
->method('get')
->with('user')
->willReturn($user);
$event->expects($this->once())
->method('getAffectedUser')
->willReturn('user');
}
$provider = $this->getProvider();
static::invokePrivate($provider, 'preParse', [$event]);
}
public function testPreParseThrows(): void {
/** @var IEvent&MockObject $event */
$event = $this->createMock(IEvent::class);
$event->expects($this->once())
->method('getApp')
->willReturn('activity');
$provider = $this->getProvider();
$this->expectException(UnknownActivityException::class);
static::invokePrivate($provider, 'preParse', [$event]);
}
public static function dataSetSubject(): array {
return [
['No placeholder', [], 'No placeholder'],
['This has one {placeholder}', ['placeholder' => ['name' => 'foobar']], 'This has one foobar'],
['This has {number} {placeholders}', ['number' => ['name' => 'two'], 'placeholders' => ['name' => 'foobars']], 'This has two foobars'],
];
}
#[DataProvider('dataSetSubject')]
public function testSetSubject(string $subject, array $parameters, string $parsedSubject): void {
$provider = $this->getProvider();
$event = $this->createMock(IEvent::class);
$event->expects($this->once())
->method('setParsedSubject')
->with($parsedSubject)
->willReturnSelf();
$event->expects($this->once())
->method('setRichSubject')
->with($subject, $parameters)
->willReturnSelf();
self::invokePrivate($provider, 'setSubjects', [$event, $subject, $parameters]);
}
public static function dataGetRoom(): array {
return [
[Room::TYPE_ONE_TO_ONE, 23, 'private-call', 'private-call', 'one2one'],
[Room::TYPE_GROUP, 42, 'group-call', 'group-call', 'group'],
[Room::TYPE_PUBLIC, 128, 'public-call', 'public-call', 'public'],
[Room::TYPE_ONE_TO_ONE, 23, '', 'a conversation', 'one2one'],
[Room::TYPE_GROUP, 42, '', 'a conversation', 'group'],
[Room::TYPE_PUBLIC, 128, '', 'a conversation', 'public'],
];
}
#[DataProvider('dataGetRoom')]
public function testGetRoom(int $type, int $id, string $name, string $expectedName, string $expectedType): void {
$provider = $this->getProvider();
$room = $this->createMock(Room::class);
$room->expects($this->once())
->method('getType')
->willReturn($type);
$room->expects($this->once())
->method('getId')
->willReturn($id);
$room->expects($this->once())
->method('getDisplayName')
->with('user')
->willReturn($expectedName);
$room->expects($this->once())
->method('getToken')
->willReturn('token');
$this->url->expects($this->once())
->method('linkToRouteAbsolute')
->with('spreed.Page.showCall', ['token' => 'token'])
->willReturn('url');
$this->assertEquals([
'type' => 'call',
'id' => $id,
'name' => $expectedName,
'call-type' => $expectedType,
'link' => 'url',
'icon-url' => '',
], self::invokePrivate($provider, 'getRoom', [$room, 'user']));
}
public static function dataGetUser(): array {
return [
['test', true, 'Test'],
['foo', false, 'foo'],
];
}
#[DataProvider('dataGetUser')]
public function testGetUser(string $uid, bool $validUser, string $name): void {
$provider = $this->getProvider();
if ($validUser) {
$this->userManager->expects($this->once())
->method('getDisplayName')
->with($uid)
->willReturn($name);
} else {
$this->userManager->expects($this->once())
->method('getDisplayName')
->with($uid)
->willReturn(null);
}
$this->assertSame([
'type' => 'user',
'id' => $uid,
'name' => $name,
], self::invokePrivate($provider, 'getUser', [$uid]));
}
}
@@ -0,0 +1,215 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Activity\Provider;
use OCA\Talk\Activity\Provider\Invitation;
use OCA\Talk\Config;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Manager;
use OCA\Talk\Room;
use OCA\Talk\Service\AvatarService;
use OCA\Talk\Service\ParticipantService;
use OCP\Activity\Exceptions\UnknownActivityException;
use OCP\Activity\IEvent;
use OCP\Activity\IManager;
use OCP\Federation\ICloudIdManager;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
/**
* Class InvitationTest
*
* @package OCA\Talk\Tests\php\Activity
*/
class InvitationTest extends TestCase {
protected IFactory&MockObject $l10nFactory;
protected IURLGenerator&MockObject $url;
protected Config&MockObject $config;
protected IManager&MockObject $activityManager;
protected IUserManager&MockObject $userManager;
protected ICloudIdManager&MockObject $cloudIdManager;
protected ParticipantService&MockObject $participantService;
protected AvatarService&MockObject $avatarService;
protected Manager&MockObject $manager;
public function setUp(): void {
parent::setUp();
$this->l10nFactory = $this->createMock(IFactory::class);
$this->url = $this->createMock(IURLGenerator::class);
$this->config = $this->createMock(Config::class);
$this->activityManager = $this->createMock(IManager::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->avatarService = $this->createMock(AvatarService::class);
$this->manager = $this->createMock(Manager::class);
}
/**
* @param string[] $methods
* @return Invitation|MockObject
*/
protected function getProvider(array $methods = []) {
if (!empty($methods)) {
return $this->getMockBuilder(Invitation::class)
->setConstructorArgs([
$this->l10nFactory,
$this->url,
$this->config,
$this->activityManager,
$this->userManager,
$this->cloudIdManager,
$this->participantService,
$this->avatarService,
$this->manager,
])
->onlyMethods($methods)
->getMock();
}
return new Invitation(
$this->l10nFactory,
$this->url,
$this->config,
$this->activityManager,
$this->userManager,
$this->cloudIdManager,
$this->participantService,
$this->avatarService,
$this->manager
);
}
public function testParseThrowsWrongSubject(): void {
/** @var IEvent&MockObject $event */
$event = $this->createMock(IEvent::class);
$event->expects($this->once())
->method('getApp')
->willReturn('spreed');
$event->expects($this->once())
->method('getSubject')
->willReturn('call');
$event->expects($this->once())
->method('getAffectedUser')
->willReturn('user');
$user = $this->createMock(IUser::class);
$this->userManager->expects($this->once())
->method('get')
->with('user')
->willReturn($user);
$this->config->expects($this->once())
->method('isDisabledForUser')
->with($user)
->willReturn(false);
$provider = $this->getProvider();
$this->expectException(UnknownActivityException::class);
$provider->parse('en', $event);
}
public static function dataParse(): array {
return [
['en', true, ['room' => 23, 'user' => 'test1'], ['actor' => ['actor-data'], 'call' => ['call-data']]],
['de', false, ['room' => 42, 'user' => 'test2'], ['actor' => ['actor-data'], 'call' => ['call-unknown']]],
];
}
#[DataProvider('dataParse')]
public function testParse(string $lang, bool $roomExists, array $params, array $expectedParams): void {
$provider = $this->getProvider(['setSubjects', 'getUser', 'getRoom', 'getFormerRoom']);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$l->expects($this->any())
->method('t')
->willReturnCallback(function ($text, $parameters = []) {
return vsprintf($text, $parameters);
});
/** @var IEvent&MockObject $event */
$event = $this->createMock(IEvent::class);
$event->expects($this->once())
->method('getApp')
->willReturn('spreed');
$event->expects($this->once())
->method('getSubject')
->willReturn('invitation');
$event->expects($this->once())
->method('getSubjectParameters')
->willReturn($params);
$event->expects($this->exactly($roomExists ? 2 : 1))
->method('getAffectedUser')
->willReturn('user');
$user = $this->createMock(IUser::class);
$this->userManager->expects($this->once())
->method('get')
->with('user')
->willReturn($user);
$this->config->expects($this->once())
->method('isDisabledForUser')
->with($user)
->willReturn(false);
if ($roomExists) {
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
$this->manager->expects($this->once())
->method('getRoomById')
->with($params['room'])
->willReturn($room);
$provider->expects($this->once())
->method('getRoom')
->with($room, 'user')
->willReturn(['call-data']);
$provider->expects($this->never())
->method('getFormerRoom');
} else {
$this->manager->expects($this->once())
->method('getRoomById')
->with($params['room'])
->willThrowException(new RoomNotFoundException());
$provider->expects($this->never())
->method('getRoom');
$provider->expects($this->once())
->method('getFormerRoom')
->with($l)
->willReturn(['call-unknown']);
}
$this->l10nFactory->expects($this->once())
->method('get')
->with('spreed', $lang)
->willReturn($l);
$provider->expects($this->once())
->method('getUser')
->with($params['user'])
->willReturn(['actor-data']);
$provider->expects($this->once())
->method('setSubjects')
->with($event, '{actor} invited you to {call}', $expectedParams);
$provider->expects($this->once())
->method('getUser')
->with($params['user'])
->willReturn(['actor-data']);
$provider->parse($lang, $event);
}
}
+80
View File
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Activity;
use OCA\Talk\Activity\Setting;
use OCP\Activity\ISetting;
use PHPUnit\Framework\Attributes\DataProvider;
use Test\TestCase;
class SettingTest extends TestCase {
public static function dataSettings(): array {
return [
[Setting::class],
];
}
#[DataProvider('dataSettings')]
public function testImplementsInterface(string $settingClass): void {
$setting = \OCP\Server::get($settingClass);
$this->assertInstanceOf(ISetting::class, $setting);
}
#[DataProvider('dataSettings')]
public function testGetIdentifier(string $settingClass): void {
/** @var ISetting $setting */
$setting = \OCP\Server::get($settingClass);
$this->assertIsString($setting->getIdentifier());
}
#[DataProvider('dataSettings')]
public function testGetName(string $settingClass): void {
/** @var ISetting $setting */
$setting = \OCP\Server::get($settingClass);
$this->assertIsString($setting->getName());
}
#[DataProvider('dataSettings')]
public function testGetPriority(string $settingClass): void {
/** @var ISetting $setting */
$setting = \OCP\Server::get($settingClass);
$priority = $setting->getPriority();
$this->assertIsInt($setting->getPriority());
$this->assertGreaterThanOrEqual(0, $priority);
$this->assertLessThanOrEqual(100, $priority);
}
#[DataProvider('dataSettings')]
public function testCanChangeStream(string $settingClass): void {
/** @var ISetting $setting */
$setting = \OCP\Server::get($settingClass);
$this->assertIsBool($setting->canChangeStream());
}
#[DataProvider('dataSettings')]
public function testIsDefaultEnabledStream(string $settingClass): void {
/** @var ISetting $setting */
$setting = \OCP\Server::get($settingClass);
$this->assertIsBool($setting->isDefaultEnabledStream());
}
#[DataProvider('dataSettings')]
public function testCanChangeMail(string $settingClass): void {
/** @var ISetting $setting */
$setting = \OCP\Server::get($settingClass);
$this->assertIsBool($setting->canChangeMail());
}
#[DataProvider('dataSettings')]
public function testIsDefaultEnabledMail(string $settingClass): void {
/** @var ISetting $setting */
$setting = \OCP\Server::get($settingClass);
$this->assertIsBool($setting->isDefaultEnabledMail());
}
}
@@ -0,0 +1,200 @@
<?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\BackgroundJob;
use OCA\Talk\BackgroundJob\CheckHostedSignalingServer;
use OCA\Talk\Config;
use OCA\Talk\Service\HostedSignalingServerService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IConfig;
use OCP\IGroup;
use OCP\IGroupManager;
use OCP\IURLGenerator;
use OCP\Notification\IManager;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class CheckHostedSignalingServerTest extends TestCase {
protected ITimeFactory&MockObject $timeFactory;
protected HostedSignalingServerService&MockObject $hostedSignalingServerService;
protected IConfig&MockObject $config;
protected IManager&MockObject $notificationManager;
protected IGroupManager&MockObject $groupManager;
protected IURLGenerator&MockObject $urlGenerator;
protected LoggerInterface&MockObject $logger;
protected Config&MockObject $talkConfig;
public function setUp(): void {
parent::setUp();
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->hostedSignalingServerService = $this->createMock(HostedSignalingServerService::class);
$this->config = $this->createMock(IConfig::class);
$this->notificationManager = $this->createMock(IManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->talkConfig = $this->createMock(Config::class);
}
public function getBackgroundJob(): CheckHostedSignalingServer {
return new CheckHostedSignalingServer(
$this->timeFactory,
$this->hostedSignalingServerService,
$this->config,
$this->notificationManager,
$this->groupManager,
$this->urlGenerator,
$this->logger,
$this->talkConfig
);
}
public function testRunWithNoChange(): void {
$backgroundJob = $this->getBackgroundJob();
$this->config
->method('getAppValue')
->willReturnMap([
['spreed', 'hosted-signaling-server-account-id', '', 'my-account-id'],
['spreed', 'hosted-signaling-server-account', '{}', '{"status": "pending"}']
]);
$this->hostedSignalingServerService->expects($this->once())
->method('fetchAccountInfo')
->willReturn(['status' => 'pending']);
self::invokePrivate($backgroundJob, 'run', ['']);
}
public function testRunWithPendingToActiveChange(): void {
$backgroundJob = $this->getBackgroundJob();
$newStatus = [
'status' => 'active',
'signaling' => [
'url' => 'signaling-url',
'secret' => 'signaling-secret',
],
];
$this->config
->method('getAppValue')
->willReturnMap([
['spreed', 'hosted-signaling-server-account-id', '', 'my-account-id'],
['spreed', 'hosted-signaling-server-account', '{}', '{"status": "pending"}']
]);
$this->config->expects($this->once())
->method('deleteAppValue')
->with('spreed', 'signaling_mode');
$expectedCalls = [
['spreed', 'signaling_servers', '{"servers":[{"server":"signaling-url","verify":true}],"secret":"signaling-secret"}'],
['spreed', 'hosted-signaling-server-account', json_encode($newStatus)],
];
$i = 0;
$this->config->expects($this->exactly(count($expectedCalls)))
->method('setAppValue')
->willReturnCallback(function () use ($expectedCalls, &$i): void {
$this->assertArrayHasKey($i, $expectedCalls);
$this->assertSame($expectedCalls[$i], func_get_args());
$i++;
});
$group = $this->createMock(IGroup::class);
$this->groupManager->expects($this->once())
->method('get')
->with('admin')
->willReturn($group);
$group->expects($this->once())
->method('getUsers')
->willReturn([]);
$this->hostedSignalingServerService->expects($this->once())
->method('fetchAccountInfo')
->willReturn($newStatus);
self::invokePrivate($backgroundJob, 'run', ['']);
}
public function testRunWithPendingToActiveIncludingStunAndTurn(): void {
$backgroundJob = $this->getBackgroundJob();
$newStatus = [
'status' => 'active',
'signaling' => [
'url' => 'signaling-url',
'secret' => 'signaling-secret',
],
'stun' => [
'servers' => [
'stun.domain.invalid:443',
'stun.domain.invalid:3478',
],
],
'turn' => [
'servers' => [
[
'server' => 'turn1.domain.invalid:443',
'secret' => 'turn-secret',
'schemes' => ['turns', 'turn'],
'protocols' => ['tcp', 'udp'],
],
[
'server' => 'turn2.domain.invalid:443',
'secret' => 'other-turn-secret',
'schemes' => ['turns'],
'protocols' => ['tcp'],
],
],
],
];
$this->config
->method('getAppValue')
->willReturnMap([
['spreed', 'hosted-signaling-server-account-id', '', 'my-account-id'],
['spreed', 'hosted-signaling-server-account', '{}', '{"status": "pending"}']
]);
$this->config->expects($this->once())
->method('deleteAppValue')
->with('spreed', 'signaling_mode');
$expectedCalls = [
['spreed', 'signaling_servers', '{"servers":[{"server":"signaling-url","verify":true}],"secret":"signaling-secret"}'],
['spreed', 'stun_servers', '["stun.domain.invalid:443","stun.domain.invalid:3478"]'],
['spreed', 'turn_servers', '[{"server":"turn1.domain.invalid:443","secret":"turn-secret","schemes":"turn,turns","protocols":"udp,tcp"},{"server":"turn2.domain.invalid:443","secret":"other-turn-secret","schemes":"turns","protocols":"tcp"}]'],
['spreed', 'hosted-signaling-server-account', json_encode($newStatus)],
];
$i = 0;
$this->config->expects($this->exactly(count($expectedCalls)))
->method('setAppValue')
->willReturnCallback(function () use ($expectedCalls, &$i): void {
$this->assertArrayHasKey($i, $expectedCalls);
$this->assertSame($expectedCalls[$i], func_get_args());
$i++;
});
$group = $this->createMock(IGroup::class);
$this->groupManager->expects($this->once())
->method('get')
->with('admin')
->willReturn($group);
$group->expects($this->once())
->method('getUsers')
->willReturn([]);
$this->hostedSignalingServerService->expects($this->once())
->method('fetchAccountInfo')
->willReturn($newStatus);
self::invokePrivate($backgroundJob, 'run', ['']);
}
}
@@ -0,0 +1,153 @@
<?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\BackgroundJob;
use OCA\Talk\BackgroundJob\LockInactiveRooms;
use OCA\Talk\Config;
use OCA\Talk\Room;
use OCA\Talk\Service\RoomService;
use OCP\AppFramework\Utility\ITimeFactory;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class LockInactiveRoomsTest extends TestCase {
protected ITimeFactory&MockObject $timeFactory;
protected RoomService&MockObject $roomService;
private Config&MockObject $appConfig;
protected LoggerInterface&MockObject $logger;
private LockInactiveRooms $job;
public function setUp(): void {
parent::setUp();
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->roomService = $this->createMock(RoomService::class);
$this->appConfig = $this->createMock(Config::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->job = new LockInactiveRooms($this->timeFactory,
$this->roomService,
$this->appConfig,
$this->logger
);
}
public function testNotEnabled(): void {
$this->appConfig->expects(self::once())
->method('getInactiveLockTime')
->willReturn(0);
$this->appConfig->expects(self::once())
->method('enableLobbyOnLockedRooms')
->willReturn(false);
$this->timeFactory->expects(self::never())
->method(self::anything());
$this->roomService->expects(self::never())
->method(self::anything());
$this->logger->expects(self::never())
->method(self::anything());
$this->job->run('t');
}
public function testNoRooms(): void {
$this->appConfig->expects(self::once())
->method('getInactiveLockTime')
->willReturn(123);
$this->appConfig->expects(self::once())
->method('enableLobbyOnLockedRooms')
->willReturn(false);
$this->timeFactory->expects(self::once())
->method('getTime');
$this->timeFactory->expects(self::once())
->method('getDateTime');
$this->roomService->expects(self::once())
->method('getInactiveRooms')
->willReturn([]);
$this->roomService->expects(self::never())
->method('setReadOnly');
$this->roomService->expects(self::never())
->method('setLobby');
$this->logger->expects(self::never())
->method(self::anything());
$this->job->run('t');
}
public function testLockRooms(): void {
$rooms = [
$this->createConfiguredMock(Room::class, [
'getReadOnly' => 0,
'getType' => Room::TYPE_PUBLIC,
]),
$this->createConfiguredMock(Room::class, [
'getReadOnly' => 0,
'getType' => Room::TYPE_GROUP,
]),
];
$this->appConfig->expects(self::once())
->method('getInactiveLockTime')
->willReturn(123);
$this->appConfig->expects(self::once())
->method('enableLobbyOnLockedRooms')
->willReturn(false);
$this->timeFactory->expects(self::once())
->method('getTime');
$this->timeFactory->expects(self::once())
->method('getDateTime');
$this->roomService->expects(self::once())
->method('getInactiveRooms')
->willReturn($rooms);
$this->roomService->expects(self::exactly(2))
->method('setReadOnly');
$this->roomService->expects(self::never())
->method('setLobby');
$this->logger->expects(self::exactly(2))
->method('debug');
$this->job->run('t');
}
public function testLockRoomsAndEnableLobby(): void {
$rooms = [
$this->createConfiguredMock(Room::class, [
'getReadOnly' => 0,
'getType' => Room::TYPE_PUBLIC,
]),
$this->createConfiguredMock(Room::class, [
'getReadOnly' => 0,
'getType' => Room::TYPE_GROUP,
]),
];
$this->appConfig->expects(self::once())
->method('getInactiveLockTime')
->willReturn(123);
$this->appConfig->expects(self::once())
->method('enableLobbyOnLockedRooms')
->willReturn(true);
$this->timeFactory->expects(self::once())
->method('getTime');
$this->timeFactory->expects(self::any())
->method('getDateTime');
$this->roomService->expects(self::once())
->method('getInactiveRooms')
->willReturn($rooms);
$this->roomService->expects(self::exactly(2))
->method('setReadOnly');
$this->roomService->expects(self::exactly(2))
->method('setLobby');
$this->logger->expects(self::exactly(4))
->method('debug');
$this->job->run('t');
}
}
@@ -0,0 +1,163 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\BackgroundJob;
use OCA\Talk\BackgroundJob\RemoveEmptyRooms;
use OCA\Talk\Federation\FederationManager;
use OCA\Talk\Manager;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RoomService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Files\Config\IUserMountCache;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class RemoveEmptyRoomsTest extends TestCase {
protected ITimeFactory&MockObject $timeFactory;
protected Manager&MockObject $manager;
protected RoomService&MockObject $roomService;
protected ParticipantService&MockObject $participantService;
protected FederationManager&MockObject $federationManager;
protected LoggerInterface&MockObject $loggerInterface;
protected IUserMountCache&MockObject $userMountCache;
public function setUp(): void {
parent::setUp();
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->manager = $this->createMock(Manager::class);
$this->roomService = $this->createMock(RoomService::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->federationManager = $this->createMock(FederationManager::class);
$this->loggerInterface = $this->createMock(LoggerInterface::class);
$this->userMountCache = $this->createMock(IUserMountCache::class);
}
public function getBackgroundJob(): RemoveEmptyRooms {
return new RemoveEmptyRooms(
$this->timeFactory,
$this->manager,
$this->roomService,
$this->participantService,
$this->federationManager,
$this->loggerInterface,
$this->userMountCache,
);
}
public function testDoDeleteRoom(): void {
$backgroundJob = $this->getBackgroundJob();
$room = $this->createMock(Room::class);
$room->method('getType')
->willReturn(Room::TYPE_GROUP);
$numDeletedRooms = self::invokePrivate($backgroundJob, 'numDeletedRooms');
$this->assertEquals(0, $numDeletedRooms, 'Invalid default quantity of rooms');
self::invokePrivate($backgroundJob, 'doDeleteRoom', [$room]);
$numDeletedRooms = self::invokePrivate($backgroundJob, 'numDeletedRooms');
$this->assertEquals(1, $numDeletedRooms, 'Invalid final quantity of rooms');
}
public static function dataDeleteIfFileIsRemoved(): array {
return [
['', [], 0],
['email', [], 0],
['file', ['fileExists'], 0],
['file', [], 1],
];
}
#[DataProvider('dataDeleteIfFileIsRemoved')]
public function testDeleteIfFileIsRemoved(string $objectType, array $fileList, int $numDeletedRoomsExpected): void {
$backgroundJob = $this->getBackgroundJob();
$numDeletedRoomsActual = self::invokePrivate($backgroundJob, 'numDeletedRooms');
$this->assertEquals(0, $numDeletedRoomsActual, 'Invalid default quantity of rooms');
$room = $this->createMock(Room::class);
$room->method('getType')
->willReturn(Room::TYPE_GROUP);
$room->method('getObjectType')
->willReturn($objectType);
$userMountCache = self::invokePrivate($backgroundJob, 'userMountCache');
$userMountCache->method('getMountsForFileId')
->willReturn($fileList);
self::invokePrivate($backgroundJob, 'deleteIfFileIsRemoved', [$room]);
$numDeletedRoomsActual = self::invokePrivate($backgroundJob, 'numDeletedRooms');
$this->assertEquals($numDeletedRoomsExpected, $numDeletedRoomsActual, 'Invalid final quantity of rooms');
}
public static function dataDeleteIfIsEmpty(): array {
return [
'room with user' => ['', 1, 0, 0],
'room with fed invite' => ['', 0, 1, 0],
'room to delete' => ['', 0, 0, 1],
'file room with user' => ['file', 1, 0, 0],
'email room with user' => ['email', 1, 0, 0],
'email room without user' => ['email', 0, 0, 1]
];
}
#[DataProvider('dataDeleteIfIsEmpty')]
public function testDeleteIfIsEmpty(string $objectType, int $actorsCount, int $inviteCount, int $numDeletedRoomsExpected): void {
$backgroundJob = $this->getBackgroundJob();
$numDeletedRoomsActual = self::invokePrivate($backgroundJob, 'numDeletedRooms');
$this->assertEquals(0, $numDeletedRoomsActual, 'Invalid default quantity of rooms');
$room = $this->createMock(Room::class);
$room->method('getType')
->willReturn(Room::TYPE_GROUP);
$room->method('getObjectType')
->willReturn($objectType);
$room->method('isFederatedConversation')
->willReturn($inviteCount > 0);
$this->federationManager->method('getNumberOfInvitations')
->with($room)
->willReturn($inviteCount);
$participantService = self::invokePrivate($backgroundJob, 'participantService');
$participantService->method('getNumberOfActors')
->willReturn($actorsCount);
self::invokePrivate($backgroundJob, 'deleteIfIsEmpty', [$room]);
$numDeletedRoomsActual = self::invokePrivate($backgroundJob, 'numDeletedRooms');
$this->assertEquals($numDeletedRoomsExpected, $numDeletedRoomsActual, 'Invalid final quantity of rooms');
}
#[DataProvider('dataCallback')]
public function testCallback(int $roomType, string $objectType, int $numDeletedRoomsExpected): void {
$backgroundJob = $this->getBackgroundJob();
$room = $this->createMock(Room::class);
$room->method('getType')
->willReturn($roomType);
$room->method('getObjectType')
->willReturn($objectType);
$backgroundJob->callback($room);
$numDeletedRoomsActual = self::invokePrivate($backgroundJob, 'numDeletedRooms');
$this->assertEquals($numDeletedRoomsExpected, $numDeletedRoomsActual, 'Invalid final quantity of rooms');
}
public static function dataCallback(): array {
return [
[Room::TYPE_CHANGELOG, '', 0],
[Room::TYPE_GROUP, 'file', 1],
];
}
}
+570
View File
@@ -0,0 +1,570 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\Unit;
use OCA\Talk\Capabilities;
use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Config;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\LiveTranscriptionService;
use OCP\App\IAppManager;
use OCP\AppFramework\Services\IAppConfig;
use OCP\Capabilities\IPublicCapability;
use OCP\ICache;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserSession;
use OCP\TaskProcessing\IManager as ITaskProcessingManager;
use OCP\TaskProcessing\TaskTypes\TextToTextFormalization;
use OCP\TaskProcessing\TaskTypes\TextToTextSummary;
use OCP\TaskProcessing\TaskTypes\TextToTextTranslate;
use OCP\Translation\ITranslationManager;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class CapabilitiesTest extends TestCase {
protected IConfig&MockObject $serverConfig;
protected Config&MockObject $talkConfig;
protected IAppConfig&MockObject $appConfig;
protected CommentsManager&MockObject $commentsManager;
protected IUserSession&MockObject $userSession;
protected IAppManager&MockObject $appManager;
protected ITranslationManager&MockObject $translationManager;
protected ITaskProcessingManager&MockObject $taskProcessingManager;
protected LiveTranscriptionService&MockObject $liveTranscriptionService;
protected ICacheFactory&MockObject $cacheFactory;
protected ICache&MockObject $talkCache;
public function setUp(): void {
parent::setUp();
$this->serverConfig = $this->createMock(IConfig::class);
$this->talkConfig = $this->createMock(Config::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->commentsManager = $this->createMock(CommentsManager::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->appManager = $this->createMock(IAppManager::class);
$this->translationManager = $this->createMock(ITranslationManager::class);
$this->taskProcessingManager = $this->createMock(ITaskProcessingManager::class);
$this->liveTranscriptionService = $this->createMock(LiveTranscriptionService::class);
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->talkCache = $this->createMock(ICache::class);
$this->cacheFactory->method('createLocal')
->with('talk::')
->willReturn($this->talkCache);
$this->commentsManager->expects($this->any())
->method('supportReactions')
->willReturn(true);
$this->appManager->expects($this->any())
->method('getAppVersion')
->with('spreed')
->willReturn('1.2.3');
}
protected function getCapabilities(): Capabilities {
return new Capabilities(
$this->serverConfig,
$this->talkConfig,
$this->appConfig,
$this->commentsManager,
$this->userSession,
$this->appManager,
$this->translationManager,
$this->taskProcessingManager,
$this->liveTranscriptionService,
$this->cacheFactory,
);
}
public function testGetCapabilitiesGuest(): void {
$capabilities = $this->getCapabilities();
$this->userSession->expects($this->once())
->method('getUser')
->willReturn(null);
$this->talkConfig->expects($this->never())
->method('isDisabledForUser');
$this->talkConfig->method('getConversationsListStyle')
->willReturn('two-lines');
$this->talkConfig->expects($this->once())
->method('isBreakoutRoomsEnabled')
->willReturn(false);
$this->serverConfig->expects($this->any())
->method('getAppValue')
->willReturnMap([
['spreed', 'max-gif-size', '3145728', '200000'],
['spreed', 'start_calls', (string)Room::START_CALL_EVERYONE, (string)Room::START_CALL_EVERYONE],
['spreed', 'session-ping-limit', '200', '200'],
['core', 'backgroundjobs_mode', 'ajax', 'cron'],
]);
$this->appConfig->method('getAppValueInt')
->willReturnMap([
['max_call_duration', 0, 0],
['retention_event_rooms', 28, 28],
['retention_phone_rooms', 7, 7],
['retention_instant_meetings', 1, 1],
['experiments_guests', 0, 0],
['summary_threshold', 100, 100],
]);
$this->talkConfig->expects($this->any())
->method('getSignalingMode')
->willReturn('internal');
$this->talkConfig->expects($this->any())
->method('getDefaultPermissions')
->willReturn(246);
$this->assertInstanceOf(IPublicCapability::class, $capabilities);
$this->assertSame([
'spreed' => [
'features' => array_merge(
Capabilities::FEATURES, [
'message-expiration',
'reactions',
]
),
'features-local' => Capabilities::LOCAL_FEATURES,
'config' => [
'attachments' => [
'allowed' => false,
],
'call' => [
'enabled' => true,
'breakout-rooms' => false,
'recording' => false,
'recording-consent' => 0,
'supported-reactions' => ['❤️', '🎉', '👏', '👋', '👍', '👎', '🔥', '😂', '🤩', '🤔', '😲', '😥'],
'can-upload-background' => false,
'sip-enabled' => false,
'sip-dialout-enabled' => false,
'default-phone-region' => '',
'can-enable-sip' => false,
'start-without-media' => false,
'max-duration' => 0,
'blur-virtual-background' => false,
'end-to-end-encryption' => false,
'live-transcription' => false,
'play-sounds' => false,
'grid-limit' => 0,
'grid-limit-enforced' => false,
'predefined-backgrounds' => [
'1_office.jpg',
'2_home.jpg',
'3_abstract.jpg',
'4_beach.jpg',
'5_park.jpg',
'6_theater.jpg',
'7_library.jpg',
'8_space_station.jpg',
],
'predefined-backgrounds-v2' => [
'/img/backgrounds/1_office.jpg',
'/img/backgrounds/2_home.jpg',
'/img/backgrounds/3_abstract.jpg',
'/img/backgrounds/4_beach.jpg',
'/img/backgrounds/5_park.jpg',
'/img/backgrounds/6_theater.jpg',
'/img/backgrounds/7_library.jpg',
'/img/backgrounds/8_space_station.jpg',
],
],
'chat' => [
'max-length' => 32000,
'read-privacy' => 0,
'has-translation-providers' => false,
'has-translation-task-providers' => false,
'typing-privacy' => 0,
'summary-threshold' => 100,
'matterbridge-enabled' => false,
],
'conversations' => [
'can-create' => false,
'force-passwords' => false,
'list-style' => 'two-lines',
'description-length' => 2000,
'retention-event' => 28,
'retention-phone' => 7,
'retention-instant-meetings' => 1,
],
'federation' => [
'enabled' => false,
'incoming-enabled' => false,
'outgoing-enabled' => false,
'only-trusted-servers' => true,
],
'previews' => [
'max-gif-size' => 200000,
],
'signaling' => [
'session-ping-limit' => 200,
'mode' => 'internal',
],
'experiments' => [
'enabled' => 0,
],
'permissions' => [
'max-default' => 254,
'max-custom' => 255,
'default' => 246,
],
],
'config-local' => Capabilities::LOCAL_CONFIGS,
'version' => '1.2.3',
],
], $capabilities->getCapabilities());
}
public static function dataGetCapabilitiesUserAllowed(): array {
return [
[true, false, 'none', true, Participant::PRIVACY_PRIVATE],
[false, true, '1 MB', true, Participant::PRIVACY_PUBLIC],
[false, true, '0 B', false, Participant::PRIVACY_PUBLIC],
];
}
#[DataProvider('dataGetCapabilitiesUserAllowed')]
public function testGetCapabilitiesUserAllowed(bool $isNotAllowed, bool $canCreate, string $quota, bool $canUpload, int $readPrivacy): void {
$capabilities = $this->getCapabilities();
$user = $this->createMock(IUser::class);
$user->expects($this->atLeastOnce())
->method('getUID')
->willReturn('uid');
$this->userSession->expects($this->once())
->method('getUser')
->willReturn($user);
$this->talkConfig->expects($this->once())
->method('isDisabledForUser')
->with($user)
->willReturn(false);
$this->talkConfig->expects($this->once())
->method('isBreakoutRoomsEnabled')
->willReturn(true);
$this->talkConfig->expects($this->once())
->method('getAttachmentFolder')
->with('uid')
->willReturn('/Talk');
$this->talkConfig->expects($this->once())
->method('isNotAllowedToCreateConversations')
->with($user)
->willReturn($isNotAllowed);
$this->talkConfig->expects($this->once())
->method('getUserReadPrivacy')
->with('uid')
->willReturn($readPrivacy);
$this->talkConfig->method('getConversationsListStyle')
->willReturn('two-lines');
$user->method('getQuota')
->willReturn($quota);
$this->taskProcessingManager->method('getAvailableTaskTypeIds')
->willReturn([TextToTextSummary::ID]);
$this->serverConfig->expects($this->any())
->method('getAppValue')
->willReturnMap([
['spreed', 'max-gif-size', '3145728', '200000'],
['spreed', 'start_calls', (string)Room::START_CALL_EVERYONE, (string)Room::START_CALL_NOONE],
['spreed', 'session-ping-limit', '200', '50'],
['core', 'backgroundjobs_mode', 'ajax', 'cron'],
]);
$this->appConfig->expects($this->any())
->method('getAppValueBool')
->willReturnMap([
['backgrounds_default_for_users', true, true],
['backgrounds_upload_users', true, true],
]);
$this->appConfig->method('getAppValueInt')
->willReturnMap([
['max_call_duration', 0, 0],
['retention_event_rooms', 28, 28],
['retention_phone_rooms', 7, 7],
['retention_instant_meetings', 1, 1],
['experiments_users', 0, 0],
['summary_threshold', 100, 100],
]);
$this->serverConfig->expects($this->any())
->method('getSystemValueString')
->willReturnMap([
['default_phone_region', '', 'DE'],
]);
$this->talkConfig->expects($this->any())
->method('getSignalingMode')
->willReturn('internal');
$this->talkConfig->expects($this->any())
->method('getDefaultPermissions')
->willReturn(246);
$this->assertInstanceOf(IPublicCapability::class, $capabilities);
$data = $capabilities->getCapabilities();
$this->assertSame([
'spreed' => [
'features' => array_merge(
Capabilities::FEATURES, [
'message-expiration',
'reactions',
'chat-summary-api',
]
),
'features-local' => Capabilities::LOCAL_FEATURES,
'config' => [
'attachments' => [
'allowed' => true,
'folder' => '/Talk',
],
'call' => [
'enabled' => false,
'breakout-rooms' => true,
'recording' => false,
'recording-consent' => 0,
'supported-reactions' => ['❤️', '🎉', '👏', '👋', '👍', '👎', '🔥', '😂', '🤩', '🤔', '😲', '😥'],
'can-upload-background' => $canUpload,
'sip-enabled' => false,
'sip-dialout-enabled' => false,
'default-phone-region' => 'DE',
'can-enable-sip' => false,
'start-without-media' => false,
'max-duration' => 0,
'blur-virtual-background' => false,
'end-to-end-encryption' => false,
'live-transcription' => false,
'play-sounds' => false,
'grid-limit' => 0,
'grid-limit-enforced' => false,
'predefined-backgrounds' => [
'1_office.jpg',
'2_home.jpg',
'3_abstract.jpg',
'4_beach.jpg',
'5_park.jpg',
'6_theater.jpg',
'7_library.jpg',
'8_space_station.jpg',
],
'predefined-backgrounds-v2' => [
'/img/backgrounds/1_office.jpg',
'/img/backgrounds/2_home.jpg',
'/img/backgrounds/3_abstract.jpg',
'/img/backgrounds/4_beach.jpg',
'/img/backgrounds/5_park.jpg',
'/img/backgrounds/6_theater.jpg',
'/img/backgrounds/7_library.jpg',
'/img/backgrounds/8_space_station.jpg',
],
],
'chat' => [
'max-length' => 32000,
'read-privacy' => $readPrivacy,
'has-translation-providers' => false,
'has-translation-task-providers' => false,
'typing-privacy' => 0,
'summary-threshold' => 100,
'matterbridge-enabled' => false,
],
'conversations' => [
'can-create' => $canCreate,
'force-passwords' => false,
'list-style' => 'two-lines',
'description-length' => 2000,
'retention-event' => 28,
'retention-phone' => 7,
'retention-instant-meetings' => 1,
],
'federation' => [
'enabled' => false,
'incoming-enabled' => false,
'outgoing-enabled' => false,
'only-trusted-servers' => true,
],
'previews' => [
'max-gif-size' => 200000,
],
'signaling' => [
'session-ping-limit' => 50,
'mode' => 'internal',
],
'experiments' => [
'enabled' => 0,
],
'permissions' => [
'max-default' => 254,
'max-custom' => 255,
'default' => 246,
],
],
'config-local' => Capabilities::LOCAL_CONFIGS,
'version' => '1.2.3',
],
], $data);
}
public function testCapabilitiesDocumentation(): void {
foreach (Capabilities::FEATURES as $feature) {
$suffix = ' - ';
if (in_array($feature, Capabilities::LOCAL_FEATURES)) {
$suffix = ' (local) - ';
}
$this->assertCapabilityIsDocumented("`$feature`" . $suffix);
}
foreach (Capabilities::CONDITIONAL_FEATURES as $feature) {
$suffix = ' - ';
if (in_array($feature, Capabilities::LOCAL_FEATURES)) {
$suffix = ' (local) - ';
}
$this->assertCapabilityIsDocumented("`$feature`" . $suffix);
}
$openapi = json_decode(file_get_contents(__DIR__ . '/../../openapi.json'), true, flags: JSON_THROW_ON_ERROR);
$configDefinition = $openapi['components']['schemas']['Capabilities']['properties']['config']['properties'] ?? null;
$this->assertIsArray($configDefinition, 'Failed to read Capabilities config from openapi.json');
$configFeatures = array_keys($configDefinition);
foreach ($configFeatures as $feature) {
foreach (array_keys($configDefinition[$feature]['properties']) as $config) {
$suffix = '';
if (in_array($config, Capabilities::LOCAL_CONFIGS[$feature])) {
$suffix = ' (local)';
}
$this->assertCapabilityIsDocumented("`config => $feature => $config`" . $suffix . ' - ');
}
}
}
protected function assertCapabilityIsDocumented(string $capability): void {
$docs = file_get_contents(__DIR__ . '/../../docs/capabilities.md');
self::assertStringContainsString($capability, $docs, 'Asserting that capability ' . $capability . ' is documented');
}
public function testGetCapabilitiesUserDisallowed(): void {
$capabilities = $this->getCapabilities();
$user = $this->createMock(IUser::class);
$this->userSession->expects($this->once())
->method('getUser')
->willReturn($user);
$this->talkConfig->expects($this->once())
->method('isDisabledForUser')
->with($user)
->willReturn(true);
$this->assertInstanceOf(IPublicCapability::class, $capabilities);
$this->assertSame([], $capabilities->getCapabilities());
}
public function testCapabilitiesHelloV2Key(): void {
$capabilities = $this->getCapabilities();
$this->talkConfig->expects($this->once())
->method('getSignalingTokenPublicKey')
->willReturn('this-is-the-key');
$data = $capabilities->getCapabilities();
$this->assertEquals('this-is-the-key', $data['spreed']['config']['signaling']['hello-v2-token-key']);
}
public static function dataTestConfigRecording(): array {
return [
[true],
[false],
];
}
#[DataProvider('dataTestConfigRecording')]
public function testConfigRecording(bool $enabled): void {
$capabilities = $this->getCapabilities();
$this->talkConfig->expects($this->once())
->method('isRecordingEnabled')
->willReturn($enabled);
$data = $capabilities->getCapabilities();
$this->assertEquals($data['spreed']['config']['call']['recording'], $enabled);
}
public static function dataTestConfigCallLiveTranscription(): array {
return [
[Config::SIGNALING_EXTERNAL, true, true],
[Config::SIGNALING_EXTERNAL, false, false],
[Config::SIGNALING_INTERNAL, true, false],
[Config::SIGNALING_INTERNAL, false, false],
];
}
#[DataProvider('dataTestConfigCallLiveTranscription')]
public function testConfigCallLiveTranscription(string $signalingMode, bool $liveTranscriptionAppEnabled, bool $expectedEnabled): void {
$capabilities = $this->getCapabilities();
$this->talkConfig->expects($this->any())
->method('getSignalingMode')
->willReturn($signalingMode);
$this->liveTranscriptionService->expects($this->any())
->method('isLiveTranscriptionAppEnabled')
->willReturn($liveTranscriptionAppEnabled);
$data = $capabilities->getCapabilities();
$this->assertEquals($data['spreed']['config']['call']['live-transcription'], $expectedEnabled);
}
public function testCapabilitiesTranslations(): void {
$capabilities = $this->getCapabilities();
$this->translationManager->method('hasProviders')
->willReturn(true);
$data = json_decode(json_encode($capabilities->getCapabilities(), JSON_THROW_ON_ERROR), true);
$this->assertEquals(true, $data['spreed']['config']['chat']['has-translation-providers']);
}
public function testCapabilitiesTranslationsTaskProviders(): void {
$capabilities = $this->getCapabilities();
$this->taskProcessingManager->method('getAvailableTaskTypeIds')
->willReturn([TextToTextTranslate::ID]);
$data = json_decode(json_encode($capabilities->getCapabilities(), JSON_THROW_ON_ERROR), true);
$this->assertEquals(true, $data['spreed']['config']['chat']['has-translation-task-providers']);
}
public function testSummaryTaskProviders(): void {
$capabilities = $this->getCapabilities();
$this->taskProcessingManager->method('getAvailableTaskTypeIds')
->willReturn([TextToTextFormalization::ID]);
$data = json_decode(json_encode($capabilities->getCapabilities(), JSON_THROW_ON_ERROR), true);
$this->assertNotContains('chat-summary-api', $data['spreed']['features']);
}
}
@@ -0,0 +1,312 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Chat\AutoComplete;
use OC\Collaboration\Collaborators\SearchResult;
use OCA\Talk\Chat\AutoComplete\SearchPlugin;
use OCA\Talk\Federation\Authenticator;
use OCA\Talk\Files\Util;
use OCA\Talk\GuestManager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\Session;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\TalkSession;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\IL10N;
use OCP\IUser;
use OCP\IUserManager;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class SearchPluginTest extends TestCase {
protected IUserManager&MockObject $userManager;
protected GuestManager&MockObject $guestManager;
protected TalkSession&MockObject $talkSession;
protected ParticipantService&MockObject $participantService;
protected Util&MockObject $util;
protected Authenticator&MockObject $federationAuthenticator;
protected IL10N&MockObject $l;
protected ?string $userId = null;
protected SearchPlugin $plugin;
public function setUp(): void {
parent::setUp();
$this->userManager = $this->createMock(IUserManager::class);
$this->guestManager = $this->createMock(GuestManager::class);
$this->talkSession = $this->createMock(TalkSession::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->util = $this->createMock(Util::class);
$this->federationAuthenticator = $this->createMock(Authenticator::class);
$this->userId = 'current';
$this->l = $this->createMock(IL10N::class);
$this->l->expects($this->any())
->method('t')
->willReturnCallback(function ($text, $parameters = []) {
return vsprintf($text, $parameters);
});
}
/**
* @param string[] $methods
* @return SearchPlugin|MockObject
*/
protected function getPlugin(array $methods = []) {
if (empty($methods)) {
return new SearchPlugin(
$this->userManager,
$this->guestManager,
$this->talkSession,
$this->participantService,
$this->util,
$this->userId,
$this->l,
$this->federationAuthenticator,
);
}
return $this->getMockBuilder(SearchPlugin::class)
->setConstructorArgs([
$this->userManager,
$this->guestManager,
$this->talkSession,
$this->participantService,
$this->util,
$this->userId,
$this->l,
$this->federationAuthenticator,
])
->onlyMethods($methods)
->getMock();
}
protected function createParticipantMock(string $uid, string $displayName, string $session = ''): Participant {
/** @var Participant&MockObject $p */
$p = $this->createMock(Participant::class);
$a = Attendee::fromRow([
'actor_type' => $uid ? 'users' : 'guests',
'actor_id' => $uid ?: sha1($session),
'display_name' => $displayName,
]);
$s = Session::fromRow([
'session_id' => $session,
]);
$p->expects($this->any())
->method('getAttendee')
->willReturn($a);
$p->expects($this->any())
->method('getSession')
->willReturn($s);
$p->expects($this->any())
->method('isGuest')
->willReturn($uid === '');
return $p;
}
public function testSearch(): void {
$result = $this->createMock(ISearchResult::class);
$room = $this->createMock(Room::class);
$this->participantService->expects($this->once())
->method('getParticipantsForRoom')
->with($room)
->willReturn([
$this->createParticipantMock('123', 'OneTwoThree'),
$this->createParticipantMock('foo', 'Foo Bar'),
$this->createParticipantMock('', 'Guest 1-6', '123456'),
$this->createParticipantMock('bar', 'Bar Tender'),
$this->createParticipantMock('', 'Guest a-f', 'abcdef'),
]);
$plugin = $this->getPlugin(['searchUsers', 'searchGuests']);
$plugin->setContext(['room' => $room]);
$plugin->expects($this->once())
->method('searchUsers')
->with('fo', ['123' => 'OneTwoThree', 'foo' => 'Foo Bar', 'bar' => 'Bar Tender'], $result)
->willReturnCallback(function ($search, $users, $result): void {
array_map(function ($user): void {
$this->assertIsString($user);
}, $users);
});
$plugin->expects($this->once())
->method('searchGuests')
->with('fo', $this->anything(), $result)
->willReturnCallback(function ($search, $guests, $result): void {
array_map(function ($guest): void {
$this->assertInstanceOf(Attendee::class, $guest);
}, $guests);
});
$plugin->search('fo', 10, 0, $result);
}
public static function dataSearchUsers(): array {
return [
['test', [], [], [], []],
['test', [
'current' => 'test',
'foo' => '',
'test' => 'Te st',
'test1' => 'Te st 1',
], [['test1' => 'Te st 1']], [['current' => 'test'], ['test' => 'Te st']]],
['test', [
'foo' => 'Test',
'bar' => 'test One',
], [['bar' => 'test One']], [['foo' => 'Test']]],
['', ['foo' => '', 'bar' => ''], [['foo' => ''], ['bar' => '']], []],
];
}
/**
* @param array<string, string> $users
*/
#[DataProvider('dataSearchUsers')]
public function testSearchUsers(string $search, array $users, array $expected, array $expectedExact): void {
$result = $this->createMock(ISearchResult::class);
$result->expects($this->once())
->method('addResultSet')
->with($this->anything(), $expected, $expectedExact);
$plugin = $this->getPlugin(['createResult']);
$plugin->method('createResult')
->willReturnCallback(function ($type, $uid, $name) {
return [$uid => $name];
});
self::invokePrivate($plugin, 'searchUsers', [$search, $users, $result]);
}
public static function dataSearchGuests(): array {
return [
['test', [], [], []],
['', ['abcdef' => ''], [['abcdef' => 'Guest']], []],
['Guest', ['abcdef' => ''], [], [['abcdef' => 'Guest']]],
['est', ['abcdef' => '', 'foobar' => 'est'], [['abcdef' => 'Guest']], [['foobar' => 'est']]],
['Ast', ['abcdef' => '', 'foobar' => 'ast'], [], [['foobar' => 'ast']]],
];
}
#[DataProvider('dataSearchGuests')]
public function testSearchGuests(string $search, array $guests, array $expected, array $expectedExact): void {
$result = $this->createMock(ISearchResult::class);
$result->expects($this->once())
->method('addResultSet')
->with($this->anything(), $expected, $expectedExact);
$attendees = [];
foreach ($guests as $actorId => $displayName) {
$attendees[] = Attendee::fromRow([
'actorId' => $actorId,
'displayName' => $displayName,
]);
}
$plugin = $this->getPlugin(['createGuestResult']);
$plugin->expects($this->any())
->method('createGuestResult')
->willReturnCallback(function ($hash, $name) {
return [$hash => $name];
});
self::invokePrivate($plugin, 'searchGuests', [$search, $attendees, $result]);
}
protected function createUserMock(array $userData) {
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->willReturn($userData['uid']);
$user->expects($this->any())
->method('getDisplayName')
->willReturn($userData['name']);
return $user;
}
public static function dataCreateResult(): array {
return [
['user', 'foo', 'bar', '', ['label' => 'bar', 'value' => ['shareType' => 'user', 'shareWith' => 'foo']]],
['user', 'test', 'Test', '', ['label' => 'Test', 'value' => ['shareType' => 'user', 'shareWith' => 'test']]],
['user', 'test', '', 'Test', ['label' => 'Test', 'value' => ['shareType' => 'user', 'shareWith' => 'test']]],
['user', 'test', '', null, ['label' => 'test', 'value' => ['shareType' => 'user', 'shareWith' => 'test']]],
];
}
#[DataProvider('dataCreateResult')]
public function testCreateResult(string $type, string $uid, string $name, ?string $managerName, array $expected): void {
if ($managerName !== null) {
$this->userManager->expects($this->any())
->method('getDisplayName')
->with($uid)
->willReturn($managerName);
} else {
$this->userManager->expects($this->any())
->method('getDisplayName')
->with($uid)
->willReturn(null);
}
$plugin = $this->getPlugin();
$this->assertEquals($expected, self::invokePrivate($plugin, 'createResult', [$type, $uid, $name]));
}
public static function dataCreateGuestResult(): array {
return [
['1234', 'foo', ['label' => 'foo', 'value' => ['shareType' => 'guest', 'shareWith' => 'guest/1234']]],
['abcd', 'bar', ['label' => 'bar', 'value' => ['shareType' => 'guest', 'shareWith' => 'guest/abcd']]],
];
}
#[DataProvider('dataCreateGuestResult')]
public function testCreateGuestResult(string $actorId, string $name, array $expected): void {
$plugin = $this->getPlugin();
$this->assertEquals($expected, self::invokePrivate($plugin, 'createGuestResult', [$actorId, $name]));
}
public static function dataSearchGroups(): array {
return [
// $search, $groups, $isGroup, $totalMatches, $totalExactMatches
['', ['groupid' => 'group'], true, 1, 0],
['groupid', ['groupid' => 'group'], true, 0, 1],
['gro', ['groupid' => 'group'], true, 1, 0],
['not', ['groupid' => 'group'], false, 0, 0],
['name', ['groupid' => 'name'], true, 0, 1],
['na', ['groupid' => 'name'], true, 1, 0],
['not', ['groupid' => 'group'], true, 0, 0],
];
}
#[DataProvider('dataSearchGroups')]
public function testSearchGroups(string $search, array $groups, bool $isGroup, int $totalMatches, int $totalExactMatches): void {
$plugin = $this->getPlugin(['createGroupResult']);
$plugin->expects($this->any())
->method('createGroupResult')
->willReturnCallback(function ($groupId) {
return [
'label' => $groupId,
'value' => [
'shareType' => 'group',
'shareWith' => 'group/' . $groupId,
],
];
});
$searchResult = new SearchResult();
self::invokePrivate($plugin, 'searchGroups', [$search, $groups, $searchResult]);
$actual = $searchResult->asArray();
$this->assertCount($totalMatches, $actual['groups']);
$this->assertCount($totalExactMatches, $actual['exact']['groups']);
}
}
@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Chat\AutoComplete;
use OCA\Talk\Chat\AutoComplete\Sorter;
use OCA\Talk\Chat\CommentsManager;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class SorterTest extends TestCase {
protected CommentsManager&MockObject $commentsManager;
protected string $userId;
protected ?Sorter $sorter = null;
protected static array $user1 = [
'label' => 'Seattle',
'value' => [
'shareType' => 'user',
'shareWith' => 'seattle',
],
];
protected static array $user2 = [
'label' => 'New York',
'value' => [
'shareType' => 'user',
'shareWith' => 'new_york',
],
];
protected static array $user3 = [
'label' => 'ttle Sea',
'value' => [
'shareType' => 'user',
'shareWith' => 'ttle_sea',
],
];
public function setUp(): void {
parent::setUp();
$this->commentsManager = $this->createMock(CommentsManager::class);
$this->sorter = new Sorter($this->commentsManager);
}
public function testGetId(): void {
$this->assertSame('talk_chat_participants', $this->sorter->getId());
}
public static function dataSort(): array {
return [
'no user posted' => ['', ['users' => [self::$user1, self::$user2]], [], ['users' => [self::$user1, self::$user2]]],
'second user posted' => ['', ['users' => [self::$user1, self::$user2]], ['new_york' => new \DateTime('2000-01-01')], ['users' => [self::$user2, self::$user1]]],
'second user posted later' => ['', ['users' => [self::$user1, self::$user2]], ['seattle' => new \DateTime('2017-01-01'), 'new_york' => new \DateTime('2018-01-01')], ['users' => [self::$user2, self::$user1]]],
'second user posted earlier' => ['', ['users' => [self::$user1, self::$user2]], ['seattle' => new \DateTime('2018-01-01'), 'new_york' => new \DateTime('2017-01-01')], ['users' => [self::$user1, self::$user2]]],
'starting match first1' => ['Sea', ['users' => [self::$user1, self::$user3]], [], ['users' => [self::$user1, self::$user3]]],
'starting match first2' => ['Sea', ['users' => [self::$user3, self::$user1]], [], ['users' => [self::$user1, self::$user3]]],
'no users' => ['', ['groups' => [self::$user1, self::$user2]], [], ['groups' => [self::$user1, self::$user2]]],
];
}
#[DataProvider('dataSort')]
public function testSort(string $search, array $toSort, array $comments, array $expected): void {
$this->commentsManager->expects(isset($toSort['users']) ? $this->once() : $this->never())
->method('getLastCommentDateByActor')
->with('chat', '23', 'comment', 'users', $this->anything())
->willReturn($comments);
$this->sorter->sort($toSort, [
'itemType' => 'chat',
'itemId' => '23',
'search' => $search,
]);
$this->assertSame($expected, $toSort);
}
}
+854
View File
@@ -0,0 +1,854 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Chat;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Chat\Notifier;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\AttendeeMapper;
use OCA\Talk\Model\Invitation;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\AttachmentService;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\PollService;
use OCA\Talk\Service\RoomService;
use OCA\Talk\Service\ThreadService;
use OCA\Talk\Share\RoomShareProvider;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Collaboration\Reference\IReferenceManager;
use OCP\Comments\IComment;
use OCP\Comments\ICommentsManager;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\ICacheFactory;
use OCP\IDBConnection;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IUser;
use OCP\Notification\IManager as INotificationManager;
use OCP\Security\RateLimiting\ILimiter;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Share\IManager;
use OCP\Share\IShare;
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 ChatManagerTest extends TestCase {
protected CommentsManager|ICommentsManager|MockObject $commentsManager;
protected IEventDispatcher&MockObject $dispatcher;
protected INotificationManager&MockObject $notificationManager;
protected IManager&MockObject $shareManager;
protected RoomShareProvider&MockObject $shareProvider;
protected ParticipantService&MockObject $participantService;
protected RoomService&MockObject $roomService;
protected PollService&MockObject $pollService;
protected ThreadService&MockObject $threadService;
protected Notifier&MockObject $notifier;
protected ITimeFactory&MockObject $timeFactory;
protected AttachmentService&MockObject $attachmentService;
protected IReferenceManager&MockObject $referenceManager;
protected ILimiter&MockObject $rateLimiter;
protected IRequest&MockObject $request;
protected LoggerInterface&MockObject $logger;
protected IL10N&MockObject $l;
protected ?ChatManager $chatManager = null;
public function setUp(): void {
parent::setUp();
$this->commentsManager = $this->createMock(CommentsManager::class);
$this->dispatcher = $this->createMock(IEventDispatcher::class);
$this->notificationManager = $this->createMock(INotificationManager::class);
$this->shareManager = $this->createMock(IManager::class);
$this->shareProvider = $this->createMock(RoomShareProvider::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->roomService = $this->createMock(RoomService::class);
$this->pollService = $this->createMock(PollService::class);
$this->threadService = $this->createMock(ThreadService::class);
$this->notifier = $this->createMock(Notifier::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->attachmentService = $this->createMock(AttachmentService::class);
$this->referenceManager = $this->createMock(IReferenceManager::class);
$this->rateLimiter = $this->createMock(ILimiter::class);
$this->request = $this->createMock(IRequest::class);
$this->l = $this->createMock(IL10N::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->l->method('n')
->willReturnCallback(function (string $singular, string $plural, int $count, array $parameters = []) {
$text = $count === 1 ? $singular : $plural;
return vsprintf(str_replace('%n', (string)$count, $text), $parameters);
});
$this->chatManager = $this->getManager();
}
/**
* @param string[] $methods
* @return ChatManager|MockObject
*/
protected function getManager(array $methods = []): ChatManager {
$cacheFactory = $this->createMock(ICacheFactory::class);
if (!empty($methods)) {
return $this->getMockBuilder(ChatManager::class)
->setConstructorArgs([
$this->commentsManager,
$this->dispatcher,
\OCP\Server::get(IDBConnection::class),
$this->notificationManager,
$this->shareManager,
$this->shareProvider,
$this->participantService,
$this->roomService,
$this->pollService,
$this->threadService,
$this->notifier,
$cacheFactory,
$this->timeFactory,
$this->attachmentService,
$this->referenceManager,
$this->rateLimiter,
$this->request,
$this->l,
$this->logger,
])
->onlyMethods($methods)
->getMock();
}
return new ChatManager(
$this->commentsManager,
$this->dispatcher,
\OCP\Server::get(IDBConnection::class),
$this->notificationManager,
$this->shareManager,
$this->shareProvider,
$this->participantService,
$this->roomService,
$this->pollService,
$this->threadService,
$this->notifier,
$cacheFactory,
$this->timeFactory,
$this->attachmentService,
$this->referenceManager,
$this->rateLimiter,
$this->request,
$this->l,
$this->logger,
);
}
private function newComment($id, string $actorType, string $actorId, \DateTime $creationDateTime, string $message): IComment {
$comment = $this->createMock(IComment::class);
$id = (string)$id;
$comment->method('getId')->willReturn($id);
$comment->method('getActorType')->willReturn($actorType);
$comment->method('getActorId')->willReturn($actorId);
$comment->method('getCreationDateTime')->willReturn($creationDateTime);
$comment->method('getMessage')->willReturn($message);
return $comment;
}
/**
* @param array $data
* @return IComment|MockObject
*/
private function newCommentFromArray(array $data): IComment {
$comment = $this->createMock(IComment::class);
foreach ($data as $key => $value) {
if ($key === 'id') {
$value = (string)$value;
}
$comment->method('get' . ucfirst($key))->willReturn($value);
}
return $comment;
}
protected function assertCommentEquals(array $data, IComment $comment): void {
if (isset($data['id'])) {
$id = $data['id'];
unset($data['id']);
$this->assertEquals($id, $comment->getId());
}
$this->assertEquals($data, [
'actorType' => $comment->getActorType(),
'actorId' => $comment->getActorId(),
'creationDateTime' => $comment->getCreationDateTime(),
'message' => $comment->getMessage(),
'referenceId' => $comment->getReferenceId(),
'parentId' => $comment->getParentId(),
]);
}
public static function dataSendMessage(): array {
return [
'simple message' => ['testUser1', 'testMessage1', '', '0'],
'reference id' => ['testUser2', 'testMessage2', 'referenceId2', '0'],
'as a reply' => ['testUser3', 'testMessage3', '', '23'],
'reply w/ ref' => ['testUser4', 'testMessage4', 'referenceId4', '23'],
];
}
#[DataProvider('dataSendMessage')]
public function testSendMessage(string $userId, string $message, string $referenceId, string $parentId): void {
$creationDateTime = new \DateTime();
$commentExpected = [
'actorType' => 'users',
'actorId' => $userId,
'creationDateTime' => $creationDateTime,
'message' => $message,
'referenceId' => $referenceId,
'parentId' => $parentId,
];
$comment = $this->newCommentFromArray($commentExpected);
if ($parentId !== '0') {
$replyTo = $this->newCommentFromArray([
'id' => $parentId,
]);
$comment->expects($this->once())
->method('setParentId')
->with($parentId);
} else {
$replyTo = null;
}
$this->commentsManager->expects($this->once())
->method('create')
->with('users', $userId, 'chat', 1234)
->willReturn($comment);
$comment->expects($this->once())
->method('setMessage')
->with($message);
$comment->expects($this->once())
->method('setCreationDateTime')
->with($creationDateTime);
$comment->expects($referenceId === '' ? $this->never() : $this->once())
->method('setReferenceId')
->with($referenceId);
$comment->expects($this->once())
->method('setVerb')
->with('comment');
$this->commentsManager->expects($this->once())
->method('save')
->with($comment);
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
->method('getId')
->willReturn(1234);
$this->notifier->expects($this->once())
->method('notifyMentionedUsers')
->with($chat, $comment);
$participant = $this->createMock(Participant::class);
$return = $this->chatManager->sendMessage($chat, $participant, 'users', $userId, $message, $creationDateTime, $replyTo, $referenceId, false);
$this->assertCommentEquals($commentExpected, $return);
}
public function testGetHistory(): void {
$offset = 1;
$limit = 42;
$expected = [
$this->newComment(110, 'users', 'testUnknownUser', new \DateTime('@' . 1000000042), 'testMessage3'),
$this->newComment(109, 'guests', 'testSpreedSession', new \DateTime('@' . 1000000023), 'testMessage2'),
$this->newComment(108, 'users', 'testUser', new \DateTime('@' . 1000000016), 'testMessage1')
];
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
->method('getId')
->willReturn(1234);
$this->commentsManager->expects($this->once())
->method('getCommentsWithVerbForObjectSinceComment')
->with('chat', 1234, [], $offset, 'desc', $limit)
->willReturn($expected);
$comments = $this->chatManager->getHistory($chat, $offset, $limit, false);
$this->assertEquals($expected, $comments);
}
public function testWaitForNewMessages(): void {
$offset = 1;
$limit = 42;
$timeout = 23;
$expected = [
$this->newComment(108, 'users', 'testUser', new \DateTime('@' . 1000000016), 'testMessage1'),
$this->newComment(109, 'guests', 'testSpreedSession', new \DateTime('@' . 1000000023), 'testMessage2'),
$this->newComment(110, 'users', 'testUnknownUser', new \DateTime('@' . 1000000042), 'testMessage3'),
];
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
->method('getId')
->willReturn(1234);
$this->commentsManager->expects($this->once())
->method('getCommentsWithVerbForObjectSinceComment')
->with('chat', 1234, [], $offset, 'asc', $limit)
->willReturn($expected);
$this->notifier->expects($this->once())
->method('markMentionNotificationsRead')
->with($chat, 'userId');
/** @var IUser&MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->willReturn('userId');
$comments = $this->chatManager->waitForNewMessages($chat, $offset, $limit, $timeout, $user, false, true);
$this->assertEquals($expected, $comments);
}
public function testWaitForNewMessagesWithWaiting(): void {
$offset = 1;
$limit = 42;
$timeout = 23;
$expected = [
$this->newComment(108, 'users', 'testUser', new \DateTime('@' . 1000000016), 'testMessage1'),
$this->newComment(109, 'guests', 'testSpreedSession', new \DateTime('@' . 1000000023), 'testMessage2'),
$this->newComment(110, 'users', 'testUnknownUser', new \DateTime('@' . 1000000042), 'testMessage3'),
];
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
->method('getId')
->willReturn(1234);
$this->commentsManager->expects($this->exactly(2))
->method('getCommentsWithVerbForObjectSinceComment')
->with('chat', 1234, [], $offset, 'asc', $limit)
->willReturnOnConsecutiveCalls(
[],
$expected
);
$this->notifier->expects($this->once())
->method('markMentionNotificationsRead')
->with($chat, 'userId');
/** @var IUser&MockObject $user */
$user = $this->createMock(IUser::class);
$user->expects($this->any())
->method('getUID')
->willReturn('userId');
$comments = $this->chatManager->waitForNewMessages($chat, $offset, $limit, $timeout, $user, false, true);
$this->assertEquals($expected, $comments);
}
public function testGetUnreadCount(): void {
/** @var Room&MockObject $chat */
$chat = $this->createMock(Room::class);
$chat->expects($this->atLeastOnce())
->method('getId')
->willReturn(23);
$this->commentsManager->expects($this->once())
->method('getNumberOfCommentsWithVerbsForObjectSinceComment')
->with('chat', 23, 42, ['comment', 'object_shared']);
$this->chatManager->getUnreadCount($chat, 42);
}
public function testDeleteMessages(): void {
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
->method('getId')
->willReturn(1234);
$this->commentsManager->expects($this->once())
->method('deleteCommentsAtObject')
->with('chat', 1234);
$this->notifier->expects($this->once())
->method('removePendingNotificationsForRoom')
->with($chat);
$this->chatManager->deleteMessages($chat);
}
public function testDeleteMessage(): void {
$mapper = new AttendeeMapper(\OCP\Server::get(IDBConnection::class));
$attendee = $mapper->createAttendeeFromRow([
'a_id' => 1,
'room_id' => 123,
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'user',
'display_name' => 'user-display',
'pin' => '',
'participant_type' => Participant::USER,
'favorite' => true,
'notification_level' => Participant::NOTIFY_MENTION,
'notification_calls' => Participant::NOTIFY_CALLS_ON,
'last_joined_call' => 0,
'last_read_message' => 0,
'last_mention_message' => 0,
'last_mention_direct' => 0,
'read_privacy' => Participant::PRIVACY_PUBLIC,
'permissions' => Attendee::PERMISSIONS_DEFAULT,
'access_token' => '',
'remote_id' => '',
'phone_number' => '',
'call_id' => '',
'invited_cloud_id' => '',
'state' => Invitation::STATE_ACCEPTED,
'unread_messages' => 0,
'last_attendee_activity' => 0,
'archived' => 0,
'important' => 0,
'sensitive' => 0,
'has_unread_threads' => false,
'has_unread_thread_mentions' => false,
'has_unread_thread_directs' => false,
]);
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
->method('getId')
->willReturn(1234);
$participant = new Participant($chat, $attendee, null);
$date = new \DateTime();
$comment = $this->createMock(IComment::class);
$comment->method('getId')
->willReturn('123456');
$comment->method('getVerb')
->willReturn('comment');
$comment->expects($this->once())
->method('setMessage');
$comment->expects($this->once())
->method('setVerb')
->with('comment_deleted');
$this->commentsManager->expects($this->once())
->method('save')
->with($comment);
$systemMessage = $this->createMock(IComment::class);
$chatManager = $this->getManager(['addSystemMessage']);
$chatManager->expects($this->once())
->method('addSystemMessage')
->with($chat, $participant, Attendee::ACTOR_USERS, 'user', $this->anything(), $this->anything(), false, null, $comment)
->willReturn($systemMessage);
$this->assertSame($systemMessage, $chatManager->deleteMessage($chat, $comment, $participant, $date));
}
public function testDeleteMessageFileShare(): void {
$mapper = new AttendeeMapper(\OCP\Server::get(IDBConnection::class));
$attendee = $mapper->createAttendeeFromRow([
'a_id' => 1,
'room_id' => 123,
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'user',
'display_name' => 'user-display',
'pin' => '',
'participant_type' => Participant::USER,
'favorite' => true,
'notification_level' => Participant::NOTIFY_MENTION,
'notification_calls' => Participant::NOTIFY_CALLS_ON,
'last_joined_call' => 0,
'last_read_message' => 0,
'last_mention_message' => 0,
'last_mention_direct' => 0,
'read_privacy' => Participant::PRIVACY_PUBLIC,
'permissions' => Attendee::PERMISSIONS_DEFAULT,
'access_token' => '',
'remote_id' => '',
'phone_number' => '',
'call_id' => '',
'invited_cloud_id' => '',
'state' => Invitation::STATE_ACCEPTED,
'unread_messages' => 0,
'last_attendee_activity' => 0,
'archived' => 0,
'important' => 0,
'sensitive' => 0,
'has_unread_threads' => false,
'has_unread_thread_mentions' => false,
'has_unread_thread_directs' => false,
]);
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
->method('getId')
->willReturn(1234);
$chat->expects($this->any())
->method('getToken')
->willReturn('T0k3N');
$participant = new Participant($chat, $attendee, null);
$date = new \DateTime();
$comment = $this->createMock(IComment::class);
$comment->method('getId')
->willReturn('123456');
$comment->method('getVerb')
->willReturn('object_shared');
$comment->expects($this->once())
->method('getMessage')
->willReturn(json_encode(['message' => 'file_shared', 'parameters' => ['share' => '42']]));
$comment->expects($this->once())
->method('setMessage');
$comment->expects($this->once())
->method('setVerb')
->with('comment_deleted');
$share = $this->createMock(IShare::class);
$share->method('getShareType')
->willReturn(IShare::TYPE_ROOM);
$share->method('getSharedWith')
->willReturn('T0k3N');
$share->method('getShareOwner')
->willReturn('user');
$this->shareManager->method('getShareById')
->with('ocRoomShare:42')
->willReturn($share);
$this->shareManager->expects($this->once())
->method('deleteShare')
->willReturn($share);
$this->commentsManager->expects($this->once())
->method('save')
->with($comment);
$systemMessage = $this->createMock(IComment::class);
$chatManager = $this->getManager(['addSystemMessage']);
$chatManager->expects($this->once())
->method('addSystemMessage')
->with($chat, $participant, Attendee::ACTOR_USERS, 'user', $this->anything(), $this->anything(), false, null, $comment)
->willReturn($systemMessage);
$this->assertSame($systemMessage, $chatManager->deleteMessage($chat, $comment, $participant, $date));
}
public function testDeleteMessageFileShareNotFound(): void {
$mapper = new AttendeeMapper(\OCP\Server::get(IDBConnection::class));
$attendee = $mapper->createAttendeeFromRow([
'a_id' => 1,
'room_id' => 123,
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'user',
'display_name' => 'user-display',
'pin' => '',
'participant_type' => Participant::USER,
'favorite' => true,
'notification_level' => Participant::NOTIFY_MENTION,
'notification_calls' => Participant::NOTIFY_CALLS_ON,
'last_joined_call' => 0,
'last_read_message' => 0,
'last_mention_message' => 0,
'last_mention_direct' => 0,
'read_privacy' => Participant::PRIVACY_PUBLIC,
'permissions' => Attendee::PERMISSIONS_DEFAULT,
'access_token' => '',
'remote_id' => '',
'phone_number' => '',
'call_id' => '',
'invited_cloud_id' => '',
'state' => Invitation::STATE_ACCEPTED,
'unread_messages' => 0,
'last_attendee_activity' => 0,
'archived' => 0,
'important' => 0,
'sensitive' => 0,
'has_unread_threads' => false,
'has_unread_thread_mentions' => false,
'has_unread_thread_directs' => false,
]);
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
->method('getId')
->willReturn(1234);
$participant = new Participant($chat, $attendee, null);
$date = new \DateTime();
$comment = $this->createMock(IComment::class);
$comment->method('getId')
->willReturn('123456');
$comment->method('getVerb')
->willReturn('object_shared');
$comment->expects($this->once())
->method('getMessage')
->willReturn(json_encode(['message' => 'file_shared', 'parameters' => ['share' => '42']]));
$this->shareManager->method('getShareById')
->with('ocRoomShare:42')
->willThrowException(new ShareNotFound());
$this->commentsManager->expects($this->never())
->method('save');
$systemMessage = $this->createMock(IComment::class);
$chatManager = $this->getManager(['addSystemMessage']);
$chatManager->expects($this->never())
->method('addSystemMessage');
$this->expectException(ShareNotFound::class);
$this->assertSame($systemMessage, $chatManager->deleteMessage($chat, $comment, $participant, $date));
}
public function testClearHistory(): void {
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
->method('getId')
->willReturn(1234);
$chat->expects($this->any())
->method('getToken')
->willReturn('t0k3n');
$this->commentsManager->expects($this->once())
->method('deleteCommentsAtObject')
->with('chat', 1234);
$this->shareProvider->expects($this->once())
->method('deleteInRoom')
->with('t0k3n');
$this->notifier->expects($this->once())
->method('removePendingNotificationsForRoom')
->with($chat, true);
$this->participantService->expects($this->once())
->method('resetChatDetails')
->with($chat);
$date = new \DateTime();
$this->timeFactory->method('getDateTime')
->willReturn($date);
$manager = $this->getManager(['addSystemMessage']);
$manager->expects($this->once())
->method('addSystemMessage')
->with(
$chat,
null,
'users',
'admin',
json_encode(['message' => 'history_cleared', 'parameters' => []]),
$date,
false
);
$manager->clearHistory($chat, 'users', 'admin');
}
public static function dataSearchIsPartOfConversationNameOrAtAll(): array {
return [
'found a in all' => [
'a', 'room', true
],
'found h in here' => [
'h', 'room', true
],
'case sensitive, not found A in all' => [
'A', 'room', false
],
'case sensitive, not found H in here' => [
'H', 'room', false
],
'non case sensitive, found r in room' => [
'R', 'room', true
],
'found r in begin of room' => [
'r', 'room', true
],
'found o in middle of room' => [
'o', 'room', true
],
'not found all in middle of text' => [
'notbeginingall', 'room', false
],
'not found here in middle of text' => [
'notbegininghere', 'room', false
],
'not found room in middle of text' => [
'notbeginingroom', 'room', false
],
];
}
#[DataProvider('dataSearchIsPartOfConversationNameOrAtAll')]
public function testSearchIsPartOfConversationNameOrAtAll(string $search, string $roomDisplayName, bool $expected): void {
$actual = self::invokePrivate($this->chatManager, 'searchIsPartOfConversationNameOrAtAll', [$search, $roomDisplayName]);
$this->assertEquals($expected, $actual);
}
public static function dataAddConversationNotify(): array {
return [
[
'',
['getType' => Room::TYPE_ONE_TO_ONE],
[],
null,
[],
],
[
'',
['getDisplayName' => 'test', 'getMentionPermissions' => 0],
['getAttendee' => Attendee::fromRow([
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'user',
])],
324,
[['id' => 'all', 'label' => 'test', 'source' => 'calls', 'mentionId' => 'all', 'details' => 'All 324 participants']]
],
[
'',
['getMentionPermissions' => 1],
['hasModeratorPermissions' => false],
null,
[],
],
[
'all',
['getDisplayName' => 'test', 'getMentionPermissions' => 0],
['getAttendee' => Attendee::fromRow([
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'user',
])],
1,
[['id' => 'all', 'label' => 'test', 'source' => 'calls', 'mentionId' => 'all']],
],
[
'all',
['getDisplayName' => 'test', 'getMentionPermissions' => 1],
[
'getAttendee' => Attendee::fromRow([
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'user',
]),
'hasModeratorPermissions' => true,
],
8,
[['id' => 'all', 'label' => 'test', 'source' => 'calls', 'mentionId' => 'all', 'details' => 'All 8 participants']],
],
[
'here',
['getDisplayName' => 'test', 'getMentionPermissions' => 0],
['getAttendee' => Attendee::fromRow([
'actor_type' => Attendee::ACTOR_GUESTS,
'actor_id' => 'guest',
])],
12,
[['id' => 'all', 'label' => 'test', 'source' => 'calls', 'mentionId' => 'all', 'details' => 'All 12 participants']],
],
];
}
#[DataProvider('dataAddConversationNotify')]
public function testAddConversationNotify(string $search, array $roomMocks, array $participantMocks, ?int $totalCount, array $expected): void {
$room = $this->createMock(Room::class);
foreach ($roomMocks as $method => $return) {
$room->expects($this->once())
->method($method)
->willReturn($return);
}
$participant = $this->createMock(Participant::class);
foreach ($participantMocks as $method => $return) {
$participant->expects($this->once())
->method($method)
->willReturn($return);
}
if ($totalCount !== null) {
$this->participantService->method('getNumberOfUsers')
->willReturn($totalCount);
}
$actual = $this->chatManager->addConversationNotify([], $search, $room, $participant);
$this->assertEquals($expected, $actual);
}
#[DataProvider('dataIsSharedFile')]
public function testIsSharedFile(string $message, bool $expected): void {
$actual = $this->chatManager->isSharedFile($message);
$this->assertEquals($expected, $actual);
}
public static function dataIsSharedFile(): array {
return [
['', false],
[json_encode([]), false],
[json_encode(['parameters' => '']), false],
[json_encode(['parameters' => []]), false],
[json_encode(['parameters' => ['share' => null]]), false],
[json_encode(['parameters' => ['share' => '']]), false],
[json_encode(['parameters' => ['share' => []]]), false],
[json_encode(['parameters' => ['share' => 0]]), false],
[json_encode(['parameters' => ['share' => 1]]), true],
];
}
#[DataProvider('dataFilterCommentsWithNonExistingFiles')]
public function testFilterCommentsWithNonExistingFiles(array $list, int $expectedCount): void {
// Transform text messages in instance of comment and mock with the message
foreach ($list as $key => $message) {
$list[$key] = $this->createMock(IComment::class);
$list[$key]->method('getMessage')
->willReturn($message);
$messageDecoded = json_decode($message, true);
if (isset($messageDecoded['parameters']['share']) && $messageDecoded['parameters']['share'] === 'notExists') {
$this->shareProvider->expects($this->once())
->method('getShareById')
->with('notExists')
->willThrowException(new ShareNotFound());
}
}
if (count($list) !== $expectedCount) {
}
$result = $this->chatManager->filterCommentsWithNonExistingFiles($list);
$this->assertCount($expectedCount, $result);
}
public static function dataFilterCommentsWithNonExistingFiles(): array {
return [
[[], 0],
[[json_encode(['parameters' => ['not a shared file']])], 1],
[[json_encode(['parameters' => ['share' => 'notExists']])], 0],
[[json_encode(['parameters' => ['share' => 1]])], 1],
];
}
}
+465
View File
@@ -0,0 +1,465 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Chat;
use OC\Comments\Comment;
use OCA\Talk\Chat\Notifier;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Files\Util;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\Session;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\ThreadService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\Notification\IManager as INotificationManager;
use OCP\Notification\INotification;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class NotifierTest extends TestCase {
protected INotificationManager&MockObject $notificationManager;
protected IUserManager&MockObject $userManager;
protected IGroupManager&MockObject $groupManager;
protected ParticipantService&MockObject $participantService;
protected ThreadService&MockObject $threadService;
protected IConfig&MockObject $config;
protected ITimeFactory&MockObject $timeFactory;
protected Util&MockObject $util;
public function setUp(): void {
parent::setUp();
$this->notificationManager = $this->createMock(INotificationManager::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->userManager
->method('userExists')
->willReturnCallback(function ($userId) {
return $userId !== 'unknownUser';
});
$this->groupManager = $this->createMock(IGroupManager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->threadService = $this->createMock(ThreadService::class);
$this->config = $this->createMock(IConfig::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->util = $this->createMock(Util::class);
}
/**
* @param string[] $methods
* @return Notifier|MockObject
*/
protected function getNotifier(array $methods = []) {
if (!empty($methods)) {
return $this->getMockBuilder(Notifier::class)
->setConstructorArgs([
$this->notificationManager,
$this->userManager,
$this->groupManager,
$this->participantService,
$this->threadService,
$this->config,
$this->timeFactory,
$this->util,
])
->onlyMethods($methods)
->getMock();
}
return new Notifier(
$this->notificationManager,
$this->userManager,
$this->groupManager,
$this->participantService,
$this->threadService,
$this->config,
$this->timeFactory,
$this->util
);
}
private function newComment($id, $actorType, $actorId, $creationDateTime, $message): IComment {
$comment = new Comment([
'id' => $id,
'object_id' => '1234',
'object_type' => 'chat',
'actor_type' => $actorType,
'actor_id' => $actorId,
'creation_date_time' => $creationDateTime,
'message' => $message,
'verb' => 'comment',
]);
return $comment;
}
/**
* @return Room|MockObject
*/
private function getRoom($settings = []) {
/** @var Room|MockObject */
$room = $this->createMock(Room::class);
$this->participantService->expects($this->any())
->method('getParticipant')
->willReturnCallback(function (Room $room, string $actorId) use ($settings): Participant {
if ($actorId === 'userNotInOneToOneChat') {
throw new ParticipantNotFoundException();
}
$attendeeRow = [
'actor_type' => 'user',
'actor_id' => $actorId,
];
if (isset($settings['attendee'][$actorId])) {
$attendeeRow = array_merge($attendeeRow, $settings['attendee'][$actorId]);
}
$attendee = Attendee::fromRow($attendeeRow);
return new Participant($room, $attendee, null);
});
return $room;
}
public static function dataNotifyMentionedUsers(): array {
return [
'no notifications' => [
'No mentions', [], [], [],
],
'notify a mentioned user' => [
'Mention @anotherUser', [], [['id' => 'anotherUser', 'type' => 'users', 'reason' => 'direct']], [['id' => 'anotherUser', 'type' => 'users', 'reason' => 'direct']],
],
'not notify mentioned user if already notified' => [
'Mention @anotherUser', [['id' => 'anotherUser', 'type' => 'users', 'reason' => 'reply']], [], [['id' => 'anotherUser', 'type' => 'users', 'reason' => 'reply']],
],
'notify mentioned Users With Long Message Start Mention' => [
'123456789 @anotherUserWithOddLengthName 123456789-123456789-123456789-123456789-123456789-123456789', [], [['id' => 'anotherUserWithOddLengthName', 'type' => 'users', 'reason' => 'direct']], [['id' => 'anotherUserWithOddLengthName', 'type' => 'users', 'reason' => 'direct']],
],
'notify mentioned users with long message middle mention' => [
'123456789-123456789-123456789-1234 @anotherUserWithOddLengthName 6789-123456789-123456789-123456789', [], [['id' => 'anotherUserWithOddLengthName', 'type' => 'users', 'reason' => 'direct']], [['id' => 'anotherUserWithOddLengthName', 'type' => 'users', 'reason' => 'direct']],
],
'notify mentioned users with long message end mention' => [
'123456789-123456789-123456789-123456789-123456789-123456789 @anotherUserWithOddLengthName 123456789', [], [['id' => 'anotherUserWithOddLengthName', 'type' => 'users', 'reason' => 'direct']], [['id' => 'anotherUserWithOddLengthName', 'type' => 'users', 'reason' => 'direct']],
],
'mention herself' => [
'Mention @testUser', [], [], [],
],
'not notify unknownuser' => [
'Mention @unknownUser', [], [], [],
],
'notify mentioned users several mentions' => [
'Mention @anotherUser, and @unknownUser, and @testUser, and @userAbleToJoin', [],
[['id' => 'anotherUser', 'type' => 'users', 'reason' => 'direct'], ['id' => 'userAbleToJoin', 'type' => 'users', 'reason' => 'direct']],
[['id' => 'anotherUser', 'type' => 'users', 'reason' => 'direct'], ['id' => 'userAbleToJoin', 'type' => 'users', 'reason' => 'direct']],
],
'notify mentioned users to user not invited to chat' => [
'Mention @userNotInOneToOneChat', [], [], [],
]
];
}
#[DataProvider('dataNotifyMentionedUsers')]
public function testNotifyMentionedUsers(string $message, array $alreadyNotifiedUsers, array $notify, array $expectedReturn): void {
if (count($notify)) {
$this->notificationManager->expects($this->exactly(count($notify)))
->method('notify');
}
$room = $this->getRoom();
$comment = $this->newComment('108', 'users', 'testUser', new \DateTime('@' . 1000000016), $message);
$notifier = $this->getNotifier([]);
$participant = $this->createMock(Participant::class);
$actual = $notifier->notifyMentionedUsers($room, $comment, $alreadyNotifiedUsers, false, $participant);
$this->assertEqualsCanonicalizing($expectedReturn, $actual);
}
public static function dataShouldParticipantBeNotified(): array {
return [
[Attendee::ACTOR_GROUPS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], false, Notifier::PRIORITY_NONE],
[Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], false, Notifier::PRIORITY_NONE],
[Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [], false, Notifier::PRIORITY_NORMAL],
[Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [['id' => 'test1', 'type' => Attendee::ACTOR_USERS]], false, Notifier::PRIORITY_NONE],
[Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [['id' => 'test1', 'type' => Attendee::ACTOR_FEDERATED_USERS]], false, Notifier::PRIORITY_NORMAL],
[Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT - 5, Attendee::ACTOR_USERS, 'test2', [], false, Notifier::PRIORITY_NONE],
[Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT + 5, Attendee::ACTOR_USERS, 'test2', [], false, Notifier::PRIORITY_NORMAL],
// Marked as important, still blocked by session and being the author, but otherwise with PRIORITY_IMPORTANT
[Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], true, Notifier::PRIORITY_NONE],
[Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [], true, Notifier::PRIORITY_IMPORTANT],
[Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT - 5, Attendee::ACTOR_USERS, 'test2', [], true, Notifier::PRIORITY_NONE],
[Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT + 5, Attendee::ACTOR_USERS, 'test2', [], true, Notifier::PRIORITY_IMPORTANT],
];
}
#[DataProvider('dataShouldParticipantBeNotified')]
public function testShouldParticipantBeNotified(string $actorType, string $actorId, ?int $sessionAge, string $commentActorType, string $commentActorId, array $alreadyNotifiedUsers, bool $isImportant, int $expected): void {
$comment = $this->createMock(IComment::class);
$comment->method('getActorType')
->willReturn($commentActorType);
$comment->method('getActorId')
->willReturn($commentActorId);
$room = $this->createMock(Room::class);
$attendee = Attendee::fromRow([
'actor_type' => $actorType,
'actor_id' => $actorId,
'important' => $isImportant,
]);
$session = null;
if ($sessionAge !== null) {
$current = 1234567;
$this->timeFactory->method('getTime')
->willReturn($current);
$session = Session::fromRow([
'last_ping' => $current - $sessionAge,
]);
}
$participant = new Participant($room, $attendee, $session);
self::assertSame($expected, self::invokePrivate($this->getNotifier(), 'shouldParticipantBeNotified', [$participant, $comment, $alreadyNotifiedUsers]));
}
public function testRemovePendingNotificationsForRoom(): void {
$notification = $this->createMock(INotification::class);
$room = $this->createMock(Room::class);
$room->expects($this->any())
->method('getToken')
->willReturn('Token123');
$this->notificationManager->expects($this->once())
->method('createNotification')
->willReturn($notification);
$notification->expects($this->once())
->method('setApp')
->with('spreed')
->willReturnSelf();
$notification->expects($this->atLeastOnce())
->method('setObject')
->with($this->anything(), 'Token123')
->willReturnSelf();
$this->notificationManager->expects($this->atLeastOnce())
->method('markProcessed')
->with($notification);
$this->getNotifier()->removePendingNotificationsForRoom($room);
}
public function testRemovePendingNotificationsForChatOnly(): void {
$notification = $this->createMock(INotification::class);
$room = $this->createMock(Room::class);
$room->expects($this->any())
->method('getToken')
->willReturn('Token123');
$this->notificationManager->expects($this->once())
->method('createNotification')
->willReturn($notification);
$notification->expects($this->once())
->method('setApp')
->with('spreed')
->willReturnSelf();
$notification->expects($this->atLeastOnce())
->method('setObject')
->with($this->anything(), 'Token123')
->willReturnSelf();
$this->notificationManager->expects($this->atLeastOnce())
->method('markProcessed')
->with($notification);
$this->getNotifier()->removePendingNotificationsForRoom($room, true);
}
public static function dataAddMentionAllToList(): array {
return [
'not notify' => [
[],
[],
0,
true,
[],
],
'preserve notify list and do not notify all' => [
[
['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
[],
0,
true,
[
['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
],
'mention all' => [
[
['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
['id' => 'all', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
[
Attendee::fromRow(['actor_id' => 'user1', 'actor_type' => Attendee::ACTOR_USERS]),
Attendee::fromRow(['actor_id' => 'user2', 'actor_type' => Attendee::ACTOR_USERS]),
],
0,
false,
[
['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
['id' => 'user2', 'type' => Attendee::ACTOR_USERS, 'reason' => 'all'],
],
],
'prevent non-moderator to notify all' => [
[
['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
['id' => 'all', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
[
Attendee::fromRow(['actor_id' => 'user1', 'actor_type' => Attendee::ACTOR_USERS]),
Attendee::fromRow(['actor_id' => 'user2', 'actor_type' => Attendee::ACTOR_USERS]),
],
1,
false,
[
['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
],
];
}
#[DataProvider('dataAddMentionAllToList')]
public function testAddMentionAllToList(array $usersToNotify, array $participants, int $mentionPermissions, bool $moderatorPermissions, array $return): void {
$room = $this->createMock(Room::class);
$room->method('getMentionPermissions')
->willReturn($mentionPermissions);
$this->participantService
->method('getActorsByType')
->willReturn($participants);
$participant = $this->createMock(Participant::class);
$participant->method('hasModeratorPermissions')
->willReturn($moderatorPermissions);
$actual = self::invokePrivate($this->getNotifier(), 'addMentionAllToList', [$room, $usersToNotify, $participant]);
$this->assertCount(count($return), $actual);
foreach ($actual as $key => $value) {
$this->assertIsArray($value);
if (array_key_exists('attendee', $value)) {
$this->assertInstanceOf(Attendee::class, $value['attendee']);
unset($value['attendee']);
}
$this->assertEqualsCanonicalizing($return[$key], $value);
}
}
public static function dataNotifyReacted(): array {
return [
'author react to own message'
=> [0, Participant::NOTIFY_MENTION, Room::TYPE_GROUP, 'testUser'],
'notify never'
=> [0, Participant::NOTIFY_NEVER, Room::TYPE_GROUP, 'testUser2'],
'notify default, not one to one'
=> [0, Participant::NOTIFY_DEFAULT, Room::TYPE_GROUP, 'testUser2'],
'notify default, one to one'
=> [1, Participant::NOTIFY_DEFAULT, Room::TYPE_ONE_TO_ONE, 'testUser2'],
'notify always'
=> [1, Participant::NOTIFY_ALWAYS, Room::TYPE_GROUP, 'testUser2'],
];
}
#[DataProvider('dataNotifyReacted')]
public function testNotifyReacted(int $notify, int $notifyType, int $roomType, string $authorId): void {
$this->notificationManager->expects($this->exactly($notify))
->method('notify');
$room = $this->getRoom([
'attendee' => [
'testUser' => [
'notificationLevel' => $notifyType,
]
]
]);
$room->method('getType')
->willReturn($roomType);
$comment = $this->newComment('108', 'users', 'testUser', new \DateTime('@' . 1000000016), 'message');
$reaction = $this->newComment('108', 'users', $authorId, new \DateTime('@' . 1000000016), 'message');
$notifier = $this->getNotifier([]);
$notifier->notifyReacted($room, $comment, $reaction);
}
public static function dataGetMentionedUsers(): array {
return [
'mention one user' => [
'Mention @anotherUser',
[
['id' => 'anotherUser', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
],
'mention two user' => [
'Mention @anotherUser, and @unknownUser',
[
['id' => 'anotherUser', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
['id' => 'unknownUser', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
],
'mention all' => [
'Mention @all',
[
['id' => 'all', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
],
'mention user, all, guest and group' => [
'mention @test, @all, @"guest/1" @"group/1"',
[
['id' => 'test', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
['id' => 'all', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
],
];
}
#[DataProvider('dataGetMentionedUsers')]
public function testGetMentionedUsers(string $message, array $expectedReturn): void {
$comment = $this->newComment('108', 'users', 'testUser', new \DateTime('@' . 1000000016), $message);
$actual = self::invokePrivate($this->getNotifier(), 'getMentionedUsers', [$comment]);
$this->assertEqualsCanonicalizing($expectedReturn, $actual);
}
public static function dataGetMentionedUserIds(): array {
$return = self::dataGetMentionedUsers();
array_walk($return, function (array &$scenario) {
array_walk($scenario[1], function (array &$params): void {
$params = $params['id'];
});
return $scenario;
});
return $return;
}
#[DataProvider('dataGetMentionedUserIds')]
public function testGetMentionedUserIds(string $message, array $expectedReturn): void {
$comment = $this->newComment('108', 'users', 'testUser', new \DateTime('@' . 1000000016), $message);
$actual = self::invokePrivate($this->getNotifier(), 'getMentionedUserIds', [$comment]);
$this->assertEqualsCanonicalizing($expectedReturn, $actual);
}
}
File diff suppressed because it is too large Load Diff
+618
View File
@@ -0,0 +1,618 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Chat\Parser;
use OCA\Talk\Chat\Parser\UserMention;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\GuestManager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\Message;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\AvatarService;
use OCA\Talk\Service\ParticipantService;
use OCP\App\IAppManager;
use OCP\Comments\IComment;
use OCP\Comments\ICommentsManager;
use OCP\Federation\ICloudId;
use OCP\Federation\ICloudIdManager;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IUserManager;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class UserMentionTest extends TestCase {
protected IAppManager&MockObject $appManager;
protected ICommentsManager&MockObject $commentsManager;
protected IUserManager&MockObject $userManager;
protected IGroupManager&MockObject $groupManager;
protected GuestManager&MockObject $guestManager;
protected AvatarService&MockObject $avatarService;
protected ICloudIdManager&MockObject $cloudIdManager;
protected ParticipantService&MockObject $participantService;
protected IL10N&MockObject $l;
protected ?UserMention $parser = null;
public function setUp(): void {
parent::setUp();
$this->appManager = $this->createMock(IAppManager::class);
$this->commentsManager = $this->createMock(ICommentsManager::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->guestManager = $this->createMock(GuestManager::class);
$this->avatarService = $this->createMock(AvatarService::class);
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->l = $this->createMock(IL10N::class);
$this->parser = new UserMention(
$this->appManager,
$this->commentsManager,
$this->userManager,
$this->groupManager,
$this->guestManager,
$this->avatarService,
$this->cloudIdManager,
$this->participantService,
$this->l,
);
}
/**
* @param array $mentions
* @param array|null $metadata
* @return MockObject|IComment
*/
private function newComment(array $mentions, ?array $metadata = null): IComment {
$comment = $this->createMock(IComment::class);
$comment->method('getMentions')->willReturn($mentions);
if ($metadata !== null) {
$comment->method('getMetaData')->willReturn($metadata);
}
return $comment;
}
public function testGetRichMessageWithoutEnrichableReferences(): void {
$comment = $this->newComment([]);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Message without enrichable references', []);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$this->assertEquals('Message without enrichable references', $chatMessage->getMessage());
$this->assertEquals([], $chatMessage->getMessageParameters());
}
public function testGetRichMessageWithSingleMention(): void {
$mentions = [
['type' => 'user', 'id' => 'testUser'],
];
$comment = $this->newComment($mentions);
$this->commentsManager->expects($this->once())
->method('resolveDisplayName')
->with('user', 'testUser')
->willReturn('testUser display name');
$this->userManager->expects($this->once())
->method('getDisplayName')
->with('testUser')
->willReturn('testUser display name');
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention to @testUser', []);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-user1' => [
'type' => 'user',
'id' => 'testUser',
'name' => 'testUser display name',
'mention-id' => 'testUser',
]
];
$this->assertEquals('Mention to {mention-user1}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public function testGetRichMessageWithDuplicatedMention(): void {
$mentions = [
['type' => 'user', 'id' => 'testUser'],
];
$comment = $this->newComment($mentions);
$this->commentsManager->expects($this->once())
->method('resolveDisplayName')
->with('user', 'testUser')
->willReturn('testUser display name');
$this->userManager->expects($this->once())
->method('getDisplayName')
->with('testUser')
->willReturn('testUser display name');
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention to @testUser and @testUser again', []);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-user1' => [
'type' => 'user',
'id' => 'testUser',
'name' => 'testUser display name',
'mention-id' => 'testUser',
]
];
$this->assertEquals('Mention to {mention-user1} and {mention-user1} again', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public static function dataGetRichMessageWithMentionsFullyIncludedInOtherMentions(): array {
// Based on valid characters from server/lib/private/User/Manager.php
return [
['testUser', 'testUser1', false],
['testUser', 'testUser1', true],
['testUser', 'testUser_1', false],
['testUser', 'testUser_1', true],
['testUser', 'testUser.1', false],
['testUser', 'testUser.1', true],
['testUser', 'testUser@1', false],
['testUser', 'testUser@1', true],
['testUser', 'testUser-1', false],
['testUser', 'testUser-1', true],
['testUser', 'testUser\'1', false],
['testUser', 'testUser\'1', true],
];
}
#[DataProvider('dataGetRichMessageWithMentionsFullyIncludedInOtherMentions')]
public function testGetRichMessageWithMentionsFullyIncludedInOtherMentions(string $baseId, string $longerId, bool $quoted): void {
$mentions = [
['type' => 'user', 'id' => $baseId],
['type' => 'user', 'id' => $longerId],
];
$comment = $this->newComment($mentions);
$this->commentsManager->expects($this->exactly(2))
->method('resolveDisplayName')
->willReturnCallback(function ($type, $id) {
return $id . ' display name';
});
$this->userManager->expects($this->exactly(2))
->method('getDisplayName')
->willReturnMap([
[$longerId, $longerId . ' display name'],
[$baseId, $baseId . ' display name']
]);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$chatMessage = new Message($room, $participant, $comment, $l);
if ($quoted) {
$chatMessage->setMessage('Mention to @"' . $baseId . '" and @"' . $longerId . '"', []);
} else {
$chatMessage->setMessage('Mention to @' . $baseId . ' and @' . $longerId, []);
}
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-user1' => [
'type' => 'user',
'id' => $longerId,
'name' => $longerId . ' display name',
'mention-id' => $longerId,
],
'mention-user2' => [
'type' => 'user',
'id' => $baseId,
'name' => $baseId . ' display name',
'mention-id' => $baseId,
],
];
$this->assertEquals('Mention to {mention-user2} and {mention-user1}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public function testGetRichMessageWithSeveralMentions(): void {
$mentions = [
['type' => 'user', 'id' => 'testUser1'],
['type' => 'user', 'id' => 'testUser2'],
['type' => 'user', 'id' => 'testUser3']
];
$comment = $this->newComment($mentions);
$this->commentsManager->expects($this->exactly(3))
->method('resolveDisplayName')
->willReturnMap([
['user', 'testUser1', 'testUser1 display name'],
['user', 'testUser2', 'testUser2 display name'],
['user', 'testUser3', 'testUser3 display name'],
]);
$this->userManager->expects($this->exactly(3))
->method('getDisplayName')
->willReturnMap([
['testUser1', 'testUser1 display name'],
['testUser2', 'testUser2 display name'],
['testUser3', 'testUser3 display name'],
]);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention to @testUser1, @testUser2, @testUser1 again and @testUser3', []);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-user1' => [
'type' => 'user',
'id' => 'testUser1',
'name' => 'testUser1 display name',
'mention-id' => 'testUser1',
],
'mention-user2' => [
'type' => 'user',
'id' => 'testUser2',
'name' => 'testUser2 display name',
'mention-id' => 'testUser2',
],
'mention-user3' => [
'type' => 'user',
'id' => 'testUser3',
'name' => 'testUser3 display name',
'mention-id' => 'testUser3',
]
];
$this->assertEquals('Mention to {mention-user1}, {mention-user2}, {mention-user1} again and {mention-user3}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public function testGetRichMessageWithNonExistingUserMention(): void {
$mentions = [
['type' => 'user', 'id' => 'me'],
['type' => 'user', 'id' => 'testUser'],
];
$comment = $this->newComment($mentions);
$this->commentsManager->expects($this->once())
->method('resolveDisplayName')
->with('user', 'testUser')
->willReturn('testUser display name');
$this->userManager->expects($this->exactly(2))
->method('getDisplayName')
->willReturnMap([
['me', null],
['testUser', 'testUser display name'],
]);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention @me to @testUser', []);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-user1' => [
'type' => 'user',
'id' => 'testUser',
'name' => 'testUser display name',
'mention-id' => 'testUser',
]
];
$this->assertEquals('Mention @me to {mention-user1}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public function testGetRichMessageWhenDisplayNameCanNotBeResolved(): void {
$mentions = [
['type' => 'user', 'id' => 'testUser'],
];
$comment = $this->newComment($mentions);
$this->commentsManager->expects($this->once())
->method('resolveDisplayName')
->willThrowException(new \OutOfBoundsException());
$this->userManager->expects($this->once())
->method('getDisplayName')
->with('testUser')
->willReturn('existing user but does not resolve later');
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention to @testUser', []);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-user1' => [
'type' => 'user',
'id' => 'testUser',
'name' => '',
'mention-id' => 'testUser',
]
];
$this->assertEquals('Mention to {mention-user1}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public function testGetRichMessageWithAtAll(): void {
$mentions = [
['type' => 'user', 'id' => 'all'],
];
$metadata = [
Message::METADATA_CAN_MENTION_ALL => true,
];
$comment = $this->newComment($mentions, $metadata);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
$room->expects($this->once())
->method('getType')
->willReturn(Room::TYPE_GROUP);
$room->expects($this->once())
->method('getToken')
->willReturn('token');
$room->expects($this->once())
->method('getDisplayName')
->willReturn('name');
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention to @all', []);
$this->avatarService->method('getAvatarUrl')
->with($room)
->willReturn('getAvatarUrl');
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-call1' => [
'type' => 'call',
'id' => 'token',
'name' => 'name',
'call-type' => 'group',
'icon-url' => 'getAvatarUrl',
'mention-id' => 'all',
]
];
$this->assertEquals('Mention to {mention-call1}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public function testGetRichMessageWithFederatedUserMention(): void {
$mentions = [
['type' => 'federated_user', 'id' => 'testUser@example.tld'],
];
$comment = $this->newComment($mentions);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention to @"federated_user/testUser@example.tld"', []);
$cloudId = $this->createMock(ICloudId::class);
$cloudId->method('getUser')
->willReturn('testUser');
$cloudId->method('getRemote')
->willReturn('example.tld');
$cloudId->method('getDisplayId')
->willReturn('Display Id');
$this->cloudIdManager->method('resolveCloudId')
->with('testUser@example.tld')
->willReturn($cloudId);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-federated-user1' => [
'type' => 'user',
'id' => 'testUser',
'name' => 'Display Id',
'server' => 'example.tld',
'mention-id' => 'federated_user/testUser@example.tld',
]
];
$this->assertEquals('Mention to {mention-federated-user1}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public function testGetRichMessageWhenAGuestWithoutNameIsMentioned(): void {
$mentions = [
['type' => 'guest', 'id' => 'guest/123456'],
];
$comment = $this->newComment($mentions);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$this->participantService->method('getParticipantByActor')
->with($room, Attendee::ACTOR_GUESTS, '123456')
->willThrowException(new ParticipantNotFoundException());
$this->l->expects($this->any())
->method('t')
->willReturnCallback(function ($text, $parameters = []) {
return vsprintf($text, $parameters);
});
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention to @"guest/123456"', []);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-guest1' => [
'type' => 'guest',
'id' => 'guest/123456',
'name' => 'Guest',
'mention-id' => 'guest/123456',
]
];
$this->assertEquals('Mention to {mention-guest1}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public function testGetRichMessageWhenAGuestWithoutNameIsMentionedMultipleTimes(): void {
$mentions = [
['type' => 'guest', 'id' => 'guest/123456'],
];
$comment = $this->newComment($mentions);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$this->participantService->method('getParticipantByActor')
->with($room, Attendee::ACTOR_GUESTS, '123456')
->willThrowException(new ParticipantNotFoundException());
$this->l->expects($this->any())
->method('t')
->willReturnCallback(function ($text, $parameters = []) {
return vsprintf($text, $parameters);
});
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention to @"guest/123456", and again @"guest/123456"', []);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-guest1' => [
'type' => 'guest',
'id' => 'guest/123456',
'name' => 'Guest',
'mention-id' => 'guest/123456',
]
];
$this->assertEquals('Mention to {mention-guest1}, and again {mention-guest1}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
public function testGetRichMessageWhenAGuestWithANameIsMentionedMultipleTimes(): void {
$mentions = [
['type' => 'guest', 'id' => 'guest/abcdef'],
];
$comment = $this->newComment($mentions);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
/** @var Participant&MockObject $participant */
$participant = $this->createMock(Participant::class);
/** @var IL10N&MockObject $l */
$l = $this->createMock(IL10N::class);
$attendee = Attendee::fromRow([
'actor_type' => 'guests',
'actor_id' => 'abcdef',
'display_name' => 'Name',
]);
$participant->method('getAttendee')
->willReturn($attendee);
$this->participantService->method('getParticipantByActor')
->with($room, Attendee::ACTOR_GUESTS, 'abcdef')
->willReturn($participant);
$this->l->expects($this->any())
->method('t')
->willReturnCallback(function ($text, $parameters = []) {
return vsprintf($text, $parameters);
});
$chatMessage = new Message($room, $participant, $comment, $l);
$chatMessage->setMessage('Mention to @"guest/abcdef", and again @"guest/abcdef"', []);
self::invokePrivate($this->parser, 'parseMessage', [$chatMessage]);
$expectedMessageParameters = [
'mention-guest1' => [
'type' => 'guest',
'id' => 'guest/abcdef',
'name' => 'Name',
'mention-id' => 'guest/abcdef',
]
];
$this->assertEquals('Mention to {mention-guest1}, and again {mention-guest1}', $chatMessage->getMessage());
$this->assertEquals($expectedMessageParameters, $chatMessage->getMessageParameters());
}
}
@@ -0,0 +1,644 @@
<?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\Chat\SystemMessage;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Chat\SystemMessage\Listener;
use OCA\Talk\Events\AParticipantModifiedEvent;
use OCA\Talk\Events\ARoomModifiedEvent;
use OCA\Talk\Events\AttendeesAddedEvent;
use OCA\Talk\Events\ParticipantModifiedEvent;
use OCA\Talk\Events\RoomModifiedEvent;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\ThreadService;
use OCA\Talk\TalkSession;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IL10N;
use OCP\IRequest;
use OCP\ISession;
use OCP\IUser;
use OCP\IUserSession;
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 ListenerTest extends TestCase {
public const DUMMY_REFERENCE_ID = 'DUMMY_REFERENCE_ID';
protected IRequest&MockObject $request;
protected ChatManager&MockObject $chatManager;
protected IUserSession&MockObject $userSession;
protected ISession&MockObject $session;
protected TalkSession&MockObject $talkSession;
protected ITimeFactory&MockObject $timeFactory;
protected IEventDispatcher&MockObject $eventDispatcher;
protected Manager&MockObject $manager;
protected ParticipantService&MockObject $participantService;
protected MessageParser&MockObject $messageParser;
protected ThreadService&MockObject $threadService;
protected LoggerInterface&MockObject $logger;
protected ?array $handlers = null;
protected ?\DateTime $dummyTime = null;
protected ?Listener $listener = null;
protected function setUp(): void {
parent::setUp();
$this->request = $this->createMock(IRequest::class);
$this->request->expects($this->any())
->method('getParam')
->with('referenceId')
->willReturn(self::DUMMY_REFERENCE_ID);
$this->dummyTime = new \DateTime();
$this->chatManager = $this->createMock(ChatManager::class);
$this->session = $this->createMock(ISession::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->talkSession = $this->createMock(TalkSession::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->timeFactory->method('getDateTime')->willReturn($this->dummyTime);
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
$this->manager = $this->createMock(Manager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->messageParser = $this->createMock(MessageParser::class);
$this->threadService = $this->createMock(ThreadService::class);
$this->logger = $this->createMock(LoggerInterface::class);
$l = $this->createMock(IL10N::class);
$l->expects($this->any())
->method('t')
->willReturnCallback(function ($string, $args) {
return vsprintf($string, $args);
});
$this->handlers = [];
$this->eventDispatcher->method('addListener')
->willReturnCallback(function ($eventName, $handler): void {
$this->handlers[$eventName] ??= [];
$this->handlers[$eventName][] = $handler;
});
$this->listener = new Listener(
$this->request,
$this->chatManager,
$this->talkSession,
$this->session,
$this->userSession,
$this->timeFactory,
$this->manager,
$this->participantService,
$this->messageParser,
$this->threadService,
$l,
$this->logger,
);
}
private function dispatch(string $eventName, $event): void {
$handlers = $this->handlers[$eventName];
$this->assertCount(1, $handlers);
$handlers[0]($event);
}
private function mockLoggedInUser($userId): IUser {
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn($userId);
$this->userSession
->method('getUser')
->willReturn($user);
return $user;
}
public function testAfterUsersAddOneToOne(): void {
$room = $this->createMock(Room::class);
$room->expects($this->any())
->method('getType')
->willReturn(Room::TYPE_ONE_TO_ONE);
$participants = [[
'actorType' => 'users',
'actorId' => 'alice_actor',
'participantType' => Participant::USER,
]];
$attendees = array_map(static fn (array $participant) => Attendee::fromParams($participant), $participants);
$event = new AttendeesAddedEvent($room, $attendees);
$this->chatManager->expects($this->never())
->method('addSystemMessage');
self::invokePrivate($this->listener, 'handle', [$event]);
}
public static function dataRoomTypes(): array {
$expectedMessages = [
[
'actorType' => 'users',
'actorId' => 'alice_actor',
'message' => ['message' => 'user_added', 'parameters' => ['user' => 'alice_actor']],
],
[
'actorType' => 'users',
'actorId' => 'alice_actor',
'message' => ['message' => 'user_added', 'parameters' => ['user' => 'bob']],
],
[
'actorType' => 'users',
'actorId' => 'alice_actor',
'message' => ['message' => 'user_added', 'parameters' => ['user' => 'carmen']],
],
[
'actorType' => 'users',
'actorId' => 'alice_actor',
'message' => ['message' => 'user_added', 'parameters' => ['user' => 'delta']],
],
];
$allParticipants = [
// guest will be ignored
[
'actorType' => 'guests'
],
// alice_actor adding self to listed channel
[
'actorType' => 'users',
'actorId' => 'alice_actor',
'participantType' => Participant::USER,
],
// alice_actor added bob
[
'actorType' => 'users',
'actorId' => 'bob',
'participantType' => Participant::USER,
],
// empty participant type
[
'actorType' => 'users',
'actorId' => 'carmen',
],
// alice_actor adding self-joined mode
[
'actorType' => 'users',
'actorId' => 'delta',
'participantType' => Participant::USER_SELF_JOINED,
],
];
return [
[Room::TYPE_GROUP, '', $allParticipants, $expectedMessages],
[Room::TYPE_PUBLIC, '', $allParticipants, $expectedMessages],
[Room::TYPE_ONE_TO_ONE, '', $allParticipants, []],
[Room::TYPE_GROUP, 'file', $allParticipants, $expectedMessages],
[Room::TYPE_PUBLIC, 'file', $allParticipants, $expectedMessages],
];
}
#[DataProvider('dataRoomTypes')]
public function testAfterUsersAdd(int $roomType, string $objectType, array $participants, array $expectedMessages): void {
$this->mockLoggedInUser('alice_actor');
$room = $this->createMock(Room::class);
$room->method('getType')->willReturn($roomType);
$room->method('getObjectType')->willReturn($objectType);
$attendees = array_map(static fn (array $participant) => Attendee::fromParams($participant), $participants);
// TODO: add all cases
$event = new AttendeesAddedEvent($room, $attendees);
$consecutive = [];
foreach ($expectedMessages as $expectedMessage) {
$consecutive[] = [
$room,
null,
$expectedMessage['actorType'],
$expectedMessage['actorId'],
json_encode($expectedMessage['message']),
$this->dummyTime,
false,
self::DUMMY_REFERENCE_ID,
null,
false,
false,
0,
];
}
if (!empty($consecutive)) {
$i = 0;
$this->chatManager->expects($this->exactly(count($consecutive)))
->method('addSystemMessage')
->willReturnCallback(function () use ($consecutive, &$i) {
$this->assertArrayHasKey($i, $consecutive);
$this->assertSame($consecutive[$i], func_get_args());
$i++;
return $this->createMock(IComment::class);
});
} else {
$this->chatManager->expects($this->never())
->method('addSystemMessage');
}
self::invokePrivate($this->listener, 'handle', [$event]);
}
public static function dataParticipantTypeChange(): array {
return [
[
Attendee::ACTOR_GROUPS,
Participant::USER,
Participant::MODERATOR,
[],
],
[
Attendee::ACTOR_USERS,
Participant::USER,
Participant::MODERATOR,
[['message' => 'moderator_promoted', 'parameters' => ['user' => 'bob_participant']]],
],
[
Attendee::ACTOR_USERS,
Participant::MODERATOR,
Participant::USER,
[['message' => 'moderator_demoted', 'parameters' => ['user' => 'bob_participant']]],
],
[
Attendee::ACTOR_GUESTS,
Participant::GUEST,
Participant::GUEST_MODERATOR,
[['message' => 'guest_moderator_promoted', 'parameters' => ['type' => 'guests', 'id' => 'bob_participant']]],
],
[
Attendee::ACTOR_GUESTS,
Participant::GUEST_MODERATOR,
Participant::GUEST,
[['message' => 'guest_moderator_demoted', 'parameters' => ['type' => 'guests', 'id' => 'bob_participant']]],
],
[
Attendee::ACTOR_EMAILS,
Participant::GUEST,
Participant::GUEST_MODERATOR,
[['message' => 'guest_moderator_promoted', 'parameters' => ['type' => 'emails', 'id' => 'bob_participant']]],
],
[
Attendee::ACTOR_EMAILS,
Participant::GUEST_MODERATOR,
Participant::GUEST,
[['message' => 'guest_moderator_demoted', 'parameters' => ['type' => 'emails', 'id' => 'bob_participant']]],
],
[
Attendee::ACTOR_USERS,
Participant::USER_SELF_JOINED,
Participant::USER,
[['message' => 'user_added', 'parameters' => ['user' => 'bob_participant']]],
],
];
}
#[DataProvider('dataParticipantTypeChange')]
public function testAfterParticipantTypeSet(string $actorType, int $oldParticipantType, int $newParticipantType, array $expectedMessages): void {
$this->mockLoggedInUser('alice_actor');
$room = $this->createMock(Room::class);
$room->method('getType')->willReturn(Room::TYPE_GROUP);
$attendee = new Attendee();
$attendee->setActorId('bob_participant');
$attendee->setActorType($actorType);
$participant = $this->createMock(Participant::class);
$participant->method('getAttendee')->willReturn($attendee);
$event = new ParticipantModifiedEvent($room, $participant, AParticipantModifiedEvent::PROPERTY_TYPE, $newParticipantType, $oldParticipantType);
foreach ($expectedMessages as $expectedMessage) {
$consecutive[] = [
$room,
null,
Attendee::ACTOR_USERS,
'alice_actor',
json_encode($expectedMessage),
$this->dummyTime,
false,
self::DUMMY_REFERENCE_ID,
null,
false,
false,
0,
];
}
if (isset($consecutive)) {
$i = 0;
$this->chatManager->expects($this->exactly(count($consecutive)))
->method('addSystemMessage')
->willReturnCallback(function () use ($consecutive, &$i) {
$this->assertArrayHasKey($i, $consecutive);
$this->assertSame($consecutive[$i], func_get_args());
$i++;
return $this->createMock(IComment::class);
});
} else {
$this->chatManager->expects($this->never())
->method('addSystemMessage');
}
self::invokePrivate($this->listener, 'handle', [$event]);
}
public static function dataCallRecordingChange(): array {
return [
[
Room::RECORDING_VIDEO_STARTING,
Room::RECORDING_NONE,
null,
null,
null,
],
[
Room::RECORDING_VIDEO_STARTING,
Room::RECORDING_NONE,
Attendee::ACTOR_USERS,
'alice',
null,
],
[
Room::RECORDING_VIDEO,
Room::RECORDING_VIDEO_STARTING,
null,
null,
['message' => 'recording_started', 'parameters' => []],
],
[
Room::RECORDING_VIDEO,
Room::RECORDING_VIDEO_STARTING,
Attendee::ACTOR_USERS,
'alice',
['message' => 'recording_started', 'parameters' => []],
],
[
Room::RECORDING_VIDEO,
Room::RECORDING_NONE,
null,
null,
['message' => 'recording_started', 'parameters' => []],
],
[
Room::RECORDING_VIDEO,
Room::RECORDING_NONE,
Attendee::ACTOR_USERS,
'alice',
['message' => 'recording_started', 'parameters' => []],
],
[
Room::RECORDING_AUDIO_STARTING,
Room::RECORDING_NONE,
null,
null,
null,
],
[
Room::RECORDING_AUDIO_STARTING,
Room::RECORDING_NONE,
Attendee::ACTOR_USERS,
'alice',
null,
],
[
Room::RECORDING_AUDIO,
Room::RECORDING_AUDIO_STARTING,
null,
null,
['message' => 'audio_recording_started', 'parameters' => []],
],
[
Room::RECORDING_AUDIO,
Room::RECORDING_AUDIO_STARTING,
Attendee::ACTOR_USERS,
'alice',
['message' => 'audio_recording_started', 'parameters' => []],
],
[
Room::RECORDING_AUDIO,
Room::RECORDING_NONE,
null,
null,
['message' => 'audio_recording_started', 'parameters' => []],
],
[
Room::RECORDING_AUDIO,
Room::RECORDING_NONE,
Attendee::ACTOR_USERS,
'alice',
['message' => 'audio_recording_started', 'parameters' => []],
],
[
Room::RECORDING_NONE,
Room::RECORDING_VIDEO_STARTING,
null,
null,
null,
],
[
Room::RECORDING_NONE,
Room::RECORDING_VIDEO_STARTING,
Attendee::ACTOR_USERS,
'bob',
null,
],
[
Room::RECORDING_NONE,
Room::RECORDING_VIDEO,
null,
null,
null,
],
[
Room::RECORDING_NONE,
Room::RECORDING_VIDEO,
Attendee::ACTOR_USERS,
'bob',
['message' => 'recording_stopped', 'parameters' => []],
],
[
Room::RECORDING_NONE,
Room::RECORDING_AUDIO_STARTING,
null,
null,
null,
],
[
Room::RECORDING_NONE,
Room::RECORDING_AUDIO_STARTING,
Attendee::ACTOR_USERS,
'bob',
null,
],
[
Room::RECORDING_NONE,
Room::RECORDING_AUDIO,
null,
null,
null,
],
[
Room::RECORDING_NONE,
Room::RECORDING_AUDIO,
Attendee::ACTOR_USERS,
'bob',
['message' => 'audio_recording_stopped', 'parameters' => []],
],
[
Room::RECORDING_FAILED,
Room::RECORDING_VIDEO_STARTING,
null,
null,
null,
],
[
Room::RECORDING_FAILED,
Room::RECORDING_AUDIO_STARTING,
null,
null,
null,
],
[
Room::RECORDING_FAILED,
Room::RECORDING_VIDEO,
null,
null,
['message' => 'recording_failed', 'parameters' => []],
],
[
Room::RECORDING_FAILED,
Room::RECORDING_AUDIO,
null,
null,
['message' => 'recording_failed', 'parameters' => []],
],
[
Room::RECORDING_VIDEO_STARTING,
Room::RECORDING_FAILED,
null,
null,
null,
],
[
Room::RECORDING_VIDEO_STARTING,
Room::RECORDING_FAILED,
Attendee::ACTOR_USERS,
'alice',
null,
],
[
Room::RECORDING_VIDEO,
Room::RECORDING_FAILED,
null,
null,
['message' => 'recording_started', 'parameters' => []],
],
[
Room::RECORDING_VIDEO,
Room::RECORDING_FAILED,
Attendee::ACTOR_USERS,
'alice',
['message' => 'recording_started', 'parameters' => []],
],
[
Room::RECORDING_AUDIO_STARTING,
Room::RECORDING_FAILED,
null,
null,
null,
],
[
Room::RECORDING_AUDIO_STARTING,
Room::RECORDING_FAILED,
Attendee::ACTOR_USERS,
'alice',
null,
],
[
Room::RECORDING_AUDIO,
Room::RECORDING_FAILED,
null,
null,
['message' => 'audio_recording_started', 'parameters' => []],
],
[
Room::RECORDING_AUDIO,
Room::RECORDING_FAILED,
Attendee::ACTOR_USERS,
'alice',
['message' => 'audio_recording_started', 'parameters' => []],
],
];
}
#[DataProvider('dataCallRecordingChange')]
public function testAfterCallRecordingSet(int $newStatus, int $oldStatus, ?string $actorType, ?string $actorId, ?array $expectedMessage): void {
$this->mockLoggedInUser('logged_in_user');
$room = $this->createMock(Room::class);
$room->expects($this->any())
->method('getType')
->willReturn(Room::TYPE_PUBLIC);
if ($actorType !== null && $actorId !== null) {
$attendee = new Attendee();
$attendee->setActorType($actorType);
$attendee->setActorId($actorId);
$participant = $this->createMock(Participant::class);
$participant->method('getAttendee')->willReturn($attendee);
$expectedActorType = $actorType;
$expectedActorId = $actorId;
} else {
$participant = null;
$expectedActorType = Attendee::ACTOR_USERS;
$expectedActorId = 'logged_in_user';
}
$event = new RoomModifiedEvent($room, ARoomModifiedEvent::PROPERTY_CALL_RECORDING, $newStatus, $oldStatus, $participant);
if ($expectedMessage !== null) {
$this->chatManager->expects($this->once())
->method('addSystemMessage')
->with(
$room,
$participant,
$expectedActorType,
$expectedActorId,
json_encode($expectedMessage),
$this->dummyTime,
false,
self::DUMMY_REFERENCE_ID,
null,
false
);
} else {
$this->chatManager->expects($this->never())
->method('addSystemMessage');
}
self::invokePrivate($this->listener, 'handle', [$event]);
}
}
@@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Collaboration\Collaborators;
use OCA\Talk\Collaboration\Collaborators\RoomPlugin;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCP\Collaboration\Collaborators\ISearchResult;
use OCP\Collaboration\Collaborators\SearchResultType;
use OCP\IUser;
use OCP\IUserSession;
use OCP\Share\IShare;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class RoomPluginTest extends TestCase {
protected ParticipantService&MockObject $participantService;
protected ?Manager $manager = null;
protected ?IUserSession $userSession = null;
protected ?IUser $user = null;
protected ?ISearchResult $searchResult = null;
protected ?RoomPlugin $plugin = null;
public function setUp(): void {
parent::setUp();
$this->manager = $this->createMock(Manager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->user = $this->createMock(IUser::class);
$this->user->expects($this->any())
->method('getUID')
->willReturn('user0');
$this->userSession = $this->createMock(IUserSession::class);
$this->userSession->expects($this->any())
->method('getUser')
->willReturn($this->user);
$this->searchResult = $this->createMock(ISearchResult::class);
$this->plugin = new RoomPlugin(
$this->manager,
$this->participantService,
$this->userSession
);
}
private function newRoom(int $type, string $token, string $name, int $permissions = Attendee::PERMISSIONS_MAX_DEFAULT): Room {
$room = $this->createMock(Room::class);
$participant = $this->createMock(Participant::class);
$room->expects($this->any())
->method('getType')
->willReturn($type);
$room->expects($this->any())
->method('getToken')
->willReturn($token);
$room->expects($this->any())
->method('getDisplayName')
->willReturn($name);
$this->participantService->expects($this->any())
->method('getParticipant')
->willReturn($participant);
$participant->expects($this->any())
->method('getPermissions')
->willReturn($permissions);
return $room;
}
private static function newResult(string $label, string $shareWith): array {
return [
'label' => $label,
'value' => [
'shareType' => IShare::TYPE_ROOM,
'shareWith' => $shareWith
]
];
}
public static function dataSearch(): array {
return [
// Empty search term with no rooms
['', 2, 0, [], [], [], false],
// Empty search term with rooms
['', 2, 0, [
[Room::TYPE_GROUP, 'roomToken', 'Room name'],
], [], [], false],
// Search term with no matches
['Unmatched search term', 2, 0, [
[Room::TYPE_GROUP, 'roomToken', 'Unmatched name'],
], [], [], false],
// Search term with single wide match
['room', 2, 0, [
[Room::TYPE_GROUP, 'roomToken', 'Room name'],
[Room::TYPE_GROUP, 'roomToken2', 'Unmatched name'],
], [], [
self::newResult('Room name', 'roomToken'),
], false],
// Chats without chat permission are not returned
['room', 2, 0, [
[Room::TYPE_GROUP, 'roomToken', 'Room name', Attendee::PERMISSIONS_MAX_DEFAULT ^ Attendee::PERMISSIONS_CHAT],
], [], [], false],
// Search term with single exact match
['room name', 2, 0, [
[Room::TYPE_GROUP, 'roomToken', 'Unmatched name'],
[Room::TYPE_GROUP, 'roomToken2', 'Room name'],
], [
self::newResult('Room name', 'roomToken2'),
], [], false],
// Search term with single exact match and single wide match
['room name', 2, 0, [
[Room::TYPE_GROUP, 'roomToken', 'Room name that also matches'],
[Room::TYPE_GROUP, 'roomToken2', 'Room name'],
], [
self::newResult('Room name', 'roomToken2'),
], [
self::newResult('Room name that also matches', 'roomToken'),
], false],
// Search term matching one-to-one rooms (not possible in practice
// as one-to-one rooms do not have a name, but it would be if they
// had, so it is included here for completeness).
['room name', 2, 0, [
[Room::TYPE_ONE_TO_ONE, 'roomToken', 'Room name that also matches'],
[Room::TYPE_ONE_TO_ONE, 'roomToken2', 'Room name'],
], [
self::newResult('Room name', 'roomToken2'),
], [
self::newResult('Room name that also matches', 'roomToken'),
], false],
// Search term matching public rooms
['room name', 2, 0, [
[Room::TYPE_PUBLIC, 'roomToken', 'Room name that also matches'],
[Room::TYPE_PUBLIC, 'roomToken2', 'Room name'],
], [
self::newResult('Room name', 'roomToken2'),
], [
self::newResult('Room name that also matches', 'roomToken'),
], false],
// Search term with several wide matches
['room', 2, 0, [
[Room::TYPE_GROUP, 'roomToken', 'Room name'],
[Room::TYPE_GROUP, 'roomToken2', 'Another room name'],
[Room::TYPE_GROUP, 'roomToken3', 'Room name'],
[Room::TYPE_GROUP, 'roomToken4', 'Another room name'],
], [], [
self::newResult('Room name', 'roomToken'),
self::newResult('Another room name', 'roomToken2'),
self::newResult('Room name', 'roomToken3'),
self::newResult('Another room name', 'roomToken4'),
], false],
// Search term with several exact matches
['room name', 2, 0, [
[Room::TYPE_GROUP, 'roomToken', 'Room name'],
[Room::TYPE_GROUP, 'roomToken2', 'Room name'],
[Room::TYPE_GROUP, 'roomToken3', 'Room name'],
[Room::TYPE_GROUP, 'roomToken4', 'Room name'],
], [
self::newResult('Room name', 'roomToken'),
self::newResult('Room name', 'roomToken2'),
self::newResult('Room name', 'roomToken3'),
self::newResult('Room name', 'roomToken4'),
], [], false],
// Search term with several matches
['room name', 2, 0, [
[Room::TYPE_GROUP, 'roomToken', 'Room name'],
[Room::TYPE_GROUP, 'roomToken2', 'Unmatched name'],
[Room::TYPE_GROUP, 'roomToken3', 'Another room name'],
[Room::TYPE_GROUP, 'roomToken4', 'Room name'],
[Room::TYPE_ONE_TO_ONE, 'roomToken5', 'Room name'],
[Room::TYPE_PUBLIC, 'roomToken6', 'Room name'],
[Room::TYPE_GROUP, 'roomToken7', 'Another unmatched name'],
[Room::TYPE_ONE_TO_ONE, 'roomToken8', 'Another unmatched name'],
[Room::TYPE_PUBLIC, 'roomToken9', 'Another unmatched name'],
[Room::TYPE_ONE_TO_ONE, 'roomToken10', 'Another room name'],
[Room::TYPE_PUBLIC, 'roomToken11', 'Another room name'],
], [
self::newResult('Room name', 'roomToken'),
self::newResult('Room name', 'roomToken4'),
self::newResult('Room name', 'roomToken5'),
self::newResult('Room name', 'roomToken6'),
], [
self::newResult('Another room name', 'roomToken3'),
self::newResult('Another room name', 'roomToken10'),
self::newResult('Another room name', 'roomToken11'),
], false],
];
}
#[DataProvider('dataSearch')]
public function testSearch(
string $searchTerm,
int $limit,
int $offset,
array $roomsForParticipant,
array $expectedMatchesExact,
array $expectedMatches,
bool $expectedHasMoreResults,
) {
$rooms = [];
foreach ($roomsForParticipant as $roomData) {
$rooms[] = call_user_func_array([$this, 'newRoom'], $roomData);
}
$this->manager->expects($this->any())
->method('getRoomsForUser')
->with('user0')
->willReturn($rooms);
$this->searchResult->expects($this->any())
->method('addResultSet')
->with(
$this->callback(
function (SearchResultType $searchResultType) {
return $searchResultType->getLabel() === 'rooms';
}
),
$expectedMatches,
$expectedMatchesExact
);
$hasMoreResults = $this->plugin->search($searchTerm, $limit, $offset, $this->searchResult);
$this->assertSame($expectedHasMoreResults, $hasMoreResults);
}
}
@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Collaboration\Resources;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Collaboration\Reference\TalkReferenceProvider;
use OCA\Talk\Manager;
use OCA\Talk\Model\ProxyCacheMessageMapper;
use OCA\Talk\Service\AvatarService;
use OCA\Talk\Service\ParticipantService;
use OCP\IL10N;
use OCP\IURLGenerator;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class TalkReferenceProviderTest extends TestCase {
protected IURLGenerator&MockObject $urlGenerator;
protected Manager&MockObject $roomManager;
protected ParticipantService&MockObject $participantService;
protected ChatManager&MockObject $chatManager;
protected ProxyCacheMessageMapper&MockObject $proxyCacheMessageMapper;
protected AvatarService&MockObject $avatarService;
protected MessageParser&MockObject $messageParser;
protected IL10N&MockObject $l;
protected ?TalkReferenceProvider $provider = null;
public function setUp(): void {
parent::setUp();
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->roomManager = $this->createMock(Manager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->chatManager = $this->createMock(ChatManager::class);
$this->proxyCacheMessageMapper = $this->createMock(ProxyCacheMessageMapper::class);
$this->avatarService = $this->createMock(AvatarService::class);
$this->messageParser = $this->createMock(MessageParser::class);
$this->l = $this->createMock(IL10N::class);
$this->provider = new TalkReferenceProvider(
$this->urlGenerator,
$this->roomManager,
$this->participantService,
$this->chatManager,
$this->proxyCacheMessageMapper,
$this->avatarService,
$this->messageParser,
$this->l,
'test'
);
}
public static function dataGetTalkAppLinkToken(): array {
return [
['https://localhost/', null],
['https://localhost/call', null],
['https://localhost/call/abcdef', ['token' => 'abcdef', 'message' => null]],
['https://localhost/call/abcdef?query=1', ['token' => 'abcdef', 'message' => null]],
['https://localhost/call/abcdef#hash=1', ['token' => 'abcdef', 'message' => null]],
['https://localhost/call/abcdef#message_123', ['token' => 'abcdef', 'message' => 123]],
['https://localhost/call/abcdef?query=1#message_123', ['token' => 'abcdef', 'message' => 123]],
['https://localhost/call/abcdef?query=1#message_123bcd', ['token' => 'abcdef', 'message' => null]],
];
}
#[DataProvider('dataGetTalkAppLinkToken')]
public function testGetTalkAppLinkToken(string $reference, ?array $expected): void {
$this->urlGenerator->expects($this->any())
->method('getAbsoluteURL')
->willReturnCallback(static fn ($url) => 'https://localhost' . $url);
$actual = self::invokePrivate($this->provider, 'getTalkAppLinkToken', [$reference]);
self::assertSame($expected, $actual);
}
}
@@ -0,0 +1,180 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2019 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Collaboration\Resources;
use OCA\Talk\Collaboration\Resources\ConversationProvider;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\AvatarService;
use OCA\Talk\Service\ParticipantService;
use OCP\Collaboration\Resources\IResource;
use OCP\Collaboration\Resources\ResourceException;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserSession;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class ConversationProviderTest extends TestCase {
protected Manager&MockObject $manager;
protected AvatarService&MockObject $avatarService;
protected ParticipantService&MockObject $participantService;
protected IUserSession&MockObject $userSession;
protected IURLGenerator&MockObject $urlGenerator;
protected ?ConversationProvider $provider = null;
public function setUp(): void {
parent::setUp();
$this->manager = $this->createMock(Manager::class);
$this->avatarService = $this->createMock(AvatarService::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->provider = new ConversationProvider(
$this->manager,
$this->avatarService,
$this->participantService,
$this->userSession,
$this->urlGenerator
);
}
public function testCanAccessResourceThrowsGuest(): void {
$resource = $this->createMock(IResource::class);
$this->expectException(ResourceException::class);
$this->expectExceptionMessage('Guests are not supported at the moment');
$this->provider->canAccessResource($resource, null);
}
public function testCanAccessResourceThrowsRoom(): void {
$user = $this->createMock(IUser::class);
$user->expects($this->once())
->method('getUID')
->willReturn('uid');
$resource = $this->createMock(IResource::class);
$resource->expects($this->once())
->method('getId')
->willReturn('token');
$this->manager->expects($this->once())
->method('getRoomForUserByToken')
->with('token', 'uid')
->willThrowException(new RoomNotFoundException());
$this->expectExceptionMessage('Conversation not found');
$this->provider->canAccessResource($resource, $user);
}
public function testCanAccessResourceThrowsParticipant(): void {
$user = $this->createMock(IUser::class);
$user->expects($this->once())
->method('getUID')
->willReturn('uid');
$resource = $this->createMock(IResource::class);
$resource->expects($this->once())
->method('getId')
->willReturn('token');
$room = $this->createMock(Room::class);
$this->participantService->expects($this->once())
->method('getParticipant')
->with($room, 'uid')
->willThrowException(new ParticipantNotFoundException());
$this->manager->expects($this->once())
->method('getRoomForUserByToken')
->with('token', 'uid')
->willReturn($room);
$this->expectExceptionMessage('Participant not found');
$this->provider->canAccessResource($resource, $user);
}
public function testCanAccessResourceParticipantNotAdded(): void {
$user = $this->createMock(IUser::class);
$user->expects($this->once())
->method('getUID')
->willReturn('uid');
$resource = $this->createMock(IResource::class);
$resource->expects($this->once())
->method('getId')
->willReturn('token');
$participant = $this->createMock(Participant::class);
$attendee = Attendee::fromRow([
'actor_type' => 'users',
'actor_id' => 'uid',
'participant_type' => Participant::USER_SELF_JOINED,
]);
$participant->expects($this->any())
->method('getAttendee')
->willReturn($attendee);
$room = $this->createMock(Room::class);
$this->participantService->expects($this->once())
->method('getParticipant')
->with($room, 'uid')
->willReturn($participant);
$this->manager->expects($this->once())
->method('getRoomForUserByToken')
->with('token', 'uid')
->willReturn($room);
$this->assertFalse($this->provider->canAccessResource($resource, $user));
}
public static function dataCanAccessResourceYes(): array {
return [
[Participant::OWNER],
[Participant::MODERATOR],
[Participant::USER],
];
}
#[DataProvider('dataCanAccessResourceYes')]
public function testCanAccessResourceYes(int $participantType): void {
$user = $this->createMock(IUser::class);
$user->expects($this->once())
->method('getUID')
->willReturn('uid');
$resource = $this->createMock(IResource::class);
$resource->expects($this->once())
->method('getId')
->willReturn('token');
$participant = $this->createMock(Participant::class);
$attendee = Attendee::fromRow([
'actor_type' => 'users',
'actor_id' => 'uid',
'participant_type' => $participantType,
]);
$participant->expects($this->any())
->method('getAttendee')
->willReturn($attendee);
$room = $this->createMock(Room::class);
$this->participantService->expects($this->once())
->method('getParticipant')
->with($room, 'uid')
->willReturn($participant);
$this->manager->expects($this->once())
->method('getRoomForUserByToken')
->with('token', 'uid')
->willReturn($room);
$this->assertTrue($this->provider->canAccessResource($resource, $user));
}
}
+167
View File
@@ -0,0 +1,167 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Command\Signaling;
use OCA\Talk\Command\Signaling\Add;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class AddTest extends TestCase {
protected IConfig&MockObject $config;
protected InputInterface&MockObject $input;
protected OutputInterface&MockObject $output;
protected Add $command;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->command = new Add($this->config);
$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}
public function testServerEmptyString(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'server') {
return '';
} elseif ($arg === 'secret') {
return 'my-test-secret';
}
throw new \Exception();
});
$this->input->method('getOption')
->with('verify')
->willReturn(true);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>Server cannot be empty.</error>'));
$this->config->expects($this->never())
->method('setAppValue');
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testSecretEmptyString(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'server') {
return 'wss://signaling.test.com';
} elseif ($arg === 'secret') {
return '';
}
throw new \Exception();
});
$this->input->method('getOption')
->with('verify')
->willReturn(true);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>Secret cannot be empty.</error>'));
$this->config->expects($this->never())
->method('setAppValue');
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAddServerToEmptyList(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'server') {
return 'wss://signaling.test.com';
} elseif ($arg === 'secret') {
return 'my-test-secret';
}
throw new \Exception();
});
$this->input->method('getOption')
->with('verify')
->willReturn(true);
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'signaling_servers')
->willReturn(json_encode([]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('signaling_servers'),
$this->equalTo(json_encode([
'servers' => [
[
'server' => 'wss://signaling.test.com',
'verify' => true
]
],
'secret' => 'my-test-secret'
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Added signaling server wss://signaling.test.com.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAddServerToNonEmptyList(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'server') {
return 'wss://signaling2.test.com';
} elseif ($arg === 'secret') {
return 'my-test-secret';
}
throw new \Exception();
});
$this->input->method('getOption')
->with('verify')
->willReturn(true);
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'signaling_servers')
->willReturn(json_encode([
'servers' => [
[
'server' => 'wss://signaling1.test.com',
'verify' => true
]
],
'secret' => 'my-test-secret'
]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('signaling_servers'),
$this->equalTo(json_encode([
'servers' => [
[
'server' => 'wss://signaling1.test.com',
'verify' => true
],
[
'server' => 'wss://signaling2.test.com',
'verify' => true
]
],
'secret' => 'my-test-secret'
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Added signaling server wss://signaling2.test.com.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
}
+146
View File
@@ -0,0 +1,146 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Command\Signaling;
use OCA\Talk\Command\Signaling\Delete;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class DeleteTest extends TestCase {
protected IConfig&MockObject $config;
protected InputInterface&MockObject $input;
protected OutputInterface&MockObject $output;
protected Delete $command;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->command = new Delete($this->config);
$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}
public function testDeleteIfEmpty(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('wss://signaling.example.com');
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'signaling_servers')
->willReturn('');
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('signaling_servers'),
$this->equalTo(json_encode([
'servers' => [],
'secret' => ''
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>There is nothing to delete.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testDelete(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('wss://signaling2.test.com');
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'signaling_servers')
->willReturn(json_encode([
'servers' => [
[
'server' => 'wss://signaling1.test.com',
'verify' => false,
],
[
'server' => 'wss://signaling2.test.com',
'verify' => false,
],
[
'server' => 'wss://signaling3.test.com',
'verify' => false,
]
],
'secret' => 'my-test-secret',
]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('signaling_servers'),
$this->equalTo(json_encode([
'servers' => [
[
'server' => 'wss://signaling1.test.com',
'verify' => false,
],
[
'server' => 'wss://signaling3.test.com',
'verify' => false,
]
],
'secret' => 'my-test-secret',
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Deleted wss://signaling2.test.com.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testNothingToDelete(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('wss://signaling4.test.com');
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'signaling_servers')
->willReturn(json_encode([
'servers' => [
[
'server' => 'wss://signaling1.test.com',
'verify' => false,
]
],
'secret' => 'my-test-secret',
]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('signaling_servers'),
$this->equalTo(json_encode([
'servers' => [
[
'server' => 'wss://signaling1.test.com',
'verify' => false,
]
],
'secret' => 'my-test-secret',
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>There is nothing to delete.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
}
@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Command\Signaling;
use OCA\Talk\Command\Signaling\ListCommand;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class ListCommandTest extends TestCase {
protected IConfig&MockObject $config;
protected InputInterface&MockObject $input;
protected OutputInterface&MockObject $output;
protected ListCommand&MockObject $command;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->command = $this->getMockBuilder(ListCommand::class)
->setConstructorArgs([$this->config])
->onlyMethods(['writeMixedInOutputFormat'])
->getMock();
$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}
public function testEmptyAppConfig(): void {
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'signaling_servers')
->willReturn(json_encode([]));
$this->command->expects($this->once())
->method('writeMixedInOutputFormat')
->with(
$this->equalTo($this->input),
$this->equalTo($this->output),
$this->equalTo([])
);
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAppConfigDataChanges(): void {
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'signaling_servers')
->willReturn(json_encode([
'servers' => [
[
'server' => 'wss://signaling.example.com',
'verify' => true
]
],
'secret' => 'my-test-secret'
]));
$this->command->expects($this->once())
->method('writeMixedInOutputFormat')
->with(
$this->equalTo($this->input),
$this->equalTo($this->output),
$this->equalTo([
'servers' => [
[
'server' => 'wss://signaling.example.com',
'verify' => true
]
],
'secret' => 'my-test-secret'
])
);
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
}
+104
View File
@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Command\Stun;
use OCA\Talk\Command\Stun\Add;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class AddTest extends TestCase {
protected IConfig&MockObject $config;
protected InputInterface&MockObject $input;
protected OutputInterface&MockObject $output;
protected Add $command;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->command = new Add($this->config);
$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}
public function testMalformedServerString(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('stun.test.com');
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>Incorrect value. Must be stunserver:port.</error>'));
$this->config->expects($this->never())
->method('setAppValue');
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAddServerToEmptyList(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('stun.test.com:443');
$this->config->method('getAppValue')
->with('spreed', 'stun_servers')
->willReturn(json_encode([]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('stun_servers'),
$this->equalTo(json_encode(['stun.test.com:443']))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Added stun.test.com:443.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAddServerToNonEmptyList(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('stun2.test.com:443');
$this->config->method('getAppValue')
->with('spreed', 'stun_servers')
->willReturn(json_encode(['stun1.test.com:443']));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('stun_servers'),
$this->equalTo(json_encode(['stun1.test.com:443', 'stun2.test.com:443']))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Added stun2.test.com:443.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAddDuplicateServer(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('stun.test.com:443');
$this->config->method('getAppValue')
->with('spreed', 'stun_servers')
->willReturn(json_encode(['stun.test.com:443']));
$this->config->expects($this->never())
->method('setAppValue');
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>Server already exists.</error>'));
$this->assertSame(1, self::invokePrivate($this->command, 'execute', [$this->input, $this->output]));
}
}
+99
View File
@@ -0,0 +1,99 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Command\Stun;
use OCA\Talk\Command\Stun\Delete;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class DeleteTest extends TestCase {
protected IConfig&MockObject $config;
protected InputInterface&MockObject $input;
protected OutputInterface&MockObject $output;
protected Delete $command;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->command = new Delete($this->config);
$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}
public function testAddDefaultServerIfEmpty(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('stun1.test.com:443');
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'stun_servers')
->willReturn('');
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('stun_servers'),
$this->equalTo(json_encode(['stun.nextcloud.com:443']))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>You deleted all STUN servers. A default STUN server was added.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testDelete(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('stun1.test.com:443');
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'stun_servers')
->willReturn(json_encode(['stun1.test.com:443', 'stun2.test.com:443']));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('stun_servers'),
$this->equalTo(json_encode(['stun2.test.com:443']))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Deleted stun1.test.com:443.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testNothingToDelete(): void {
$this->input->method('getArgument')
->with('server')
->willReturn('stun3.test.com:443');
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'stun_servers')
->willReturn(json_encode(['stun1.test.com:443']));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('stun_servers'),
$this->equalTo(json_encode(['stun1.test.com:443']))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>There is nothing to delete.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
}
@@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Command\Stun;
use OCA\Talk\Command\Stun\ListCommand;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class ListCommandTest extends TestCase {
protected IConfig&MockObject $config;
protected InputInterface&MockObject $input;
protected OutputInterface&MockObject $output;
protected ListCommand&MockObject $command;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->command = $this->getMockBuilder(ListCommand::class)
->setConstructorArgs([$this->config])
->onlyMethods(['writeArrayInOutputFormat'])
->getMock();
$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}
public function testEmptyAppConfig(): void {
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'stun_servers')
->willReturn(json_encode([]));
$this->command->expects($this->once())
->method('writeArrayInOutputFormat')
->with(
$this->equalTo($this->input),
$this->equalTo($this->output),
$this->equalTo([])
);
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAppConfigDataChanges(): void {
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'stun_servers')
->willReturn(json_encode([
'stun.test.com:443',
'stun2.test.com:443'
]));
$this->command->expects($this->once())
->method('writeArrayInOutputFormat')
->with(
$this->equalTo($this->input),
$this->equalTo($this->output),
$this->equalTo([
'stun.test.com:443',
'stun2.test.com:443'
])
);
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
}
+419
View File
@@ -0,0 +1,419 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Command\Turn;
use OCA\Talk\Command\Turn\Add;
use OCP\IConfig;
use OCP\Security\ISecureRandom;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class AddTest extends TestCase {
protected IConfig&MockObject $config;
protected ISecureRandom&MockObject $secureRandom;
protected InputInterface&MockObject $input;
protected OutputInterface&MockObject $output;
protected Add $command;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->secureRandom = $this->createMock(ISecureRandom::class);
$this->command = new Add($this->config, $this->secureRandom);
$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}
public function testServerEmptyString(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return '';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return 'my-test-secret';
} elseif ($arg === 'generate-secret') {
return false;
}
throw new \Exception();
});
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>Server cannot be empty.</error>'));
$this->config->expects($this->never())
->method('setAppValue');
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testSecretEmpty(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn.test.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return '';
} elseif ($arg === 'generate-secret') {
return false;
}
throw new \Exception();
});
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>Secret cannot be empty.</error>'));
$this->config->expects($this->never())
->method('setAppValue');
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testGenerateSecret(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn.test.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return null;
} elseif ($arg === 'generate-secret') {
return true;
}
throw new \Exception();
});
$this->secureRandom->expects($this->once())
->method('generate')
->willReturn('O2vWVFk5QRdJ/9clK4XIHbkxYXvVe6ySggANw4TG/B/HCtFzpi7v4GVNB/6wUZvA13v2EN4WgDk+gjATk9zhhc7B6FzxLNWOlQFBADg2aYHb+Ozse2BABDk3VUHCR+W9');
$this->config->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('turn_servers'),
$this->equalTo(json_encode([
[
'schemes' => 'turn,turns',
'server' => 'turn.test.com',
'secret' => 'O2vWVFk5QRdJ/9clK4XIHbkxYXvVe6ySggANw4TG/B/HCtFzpi7v4GVNB/6wUZvA13v2EN4WgDk+gjATk9zhhc7B6FzxLNWOlQFBADg2aYHb+Ozse2BABDk3VUHCR+W9',
'protocols' => 'udp,tcp'
]
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Added turn.test.com.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testSecretAndGenerateSecretOptions(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn.test.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return 'my-test-secret';
} elseif ($arg === 'generate-secret') {
return true;
}
throw new \Exception();
});
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>You must provide --secret or --generate-secret.</error>'));
$this->config->expects($this->never())
->method('setAppValue');
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testInvalidSchemesString(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'invalid-scheme';
} elseif ($arg === 'server') {
return 'turn.test.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return 'my-test-secret';
} elseif ($arg === 'generate-secret') {
return false;
}
throw new \Exception();
});
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>Not allowed schemes, must be turn or turns or turn,turns.</error>'));
$this->config->expects($this->never())
->method('setAppValue');
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testInvalidProtocolsString(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn.test.com';
} elseif ($arg === 'protocols') {
return 'invalid-protocol';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return 'my-test-secret';
} elseif ($arg === 'generate-secret') {
return false;
}
throw new \Exception();
});
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>Not allowed protocols, must be udp or tcp or udp,tcp.</error>'));
$this->config->expects($this->never())
->method('setAppValue');
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAddServerToEmptyList(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn.test.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return 'my-test-secret';
} elseif ($arg === 'generate-secret') {
return false;
}
throw new \Exception();
});
$this->config->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('turn_servers'),
$this->equalTo(json_encode([
[
'schemes' => 'turn,turns',
'server' => 'turn.test.com',
'secret' => 'my-test-secret',
'protocols' => 'udp,tcp'
]
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Added turn.test.com.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAddServerToNonEmptyList(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn2.test.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return 'my-test-secret-2';
} elseif ($arg === 'generate-secret') {
return false;
}
throw new \Exception();
});
$this->config->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([
[
'schemes' => 'turn',
'server' => 'turn1.test.com',
'secret' => 'my-test-secret-1',
'protocols' => 'udp,tcp'
]
]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('turn_servers'),
$this->equalTo(json_encode([
[
'schemes' => 'turn',
'server' => 'turn1.test.com',
'secret' => 'my-test-secret-1',
'protocols' => 'udp,tcp'
],
[
'schemes' => 'turn,turns',
'server' => 'turn2.test.com',
'secret' => 'my-test-secret-2',
'protocols' => 'udp,tcp'
]
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Added turn2.test.com.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testServerSanitization(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'https://turn.test.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return 'my-test-secret';
} elseif ($arg === 'generate-secret') {
return false;
}
throw new \Exception();
});
$this->config->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('turn_servers'),
$this->equalTo(json_encode([
[
'schemes' => 'turn,turns',
'server' => 'turn.test.com',
'secret' => 'my-test-secret',
'protocols' => 'udp,tcp'
]
]))
);
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAddDuplicateServer(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn.test.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->input->method('getOption')
->willReturnCallback(function ($arg) {
if ($arg === 'secret') {
return 'my-test-secret';
} elseif ($arg === 'generate-secret') {
return false;
}
throw new \Exception();
});
$this->config->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([[
'schemes' => 'turn,turns',
'server' => 'turn.test.com',
'secret' => 'my-test-secret',
'protocols' => 'udp,tcp'
]]));
$this->config->expects($this->never())
->method('setAppValue');
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<error>Server already exists with the same configuration.</error>'));
$this->assertSame(1, self::invokePrivate($this->command, 'execute', [$this->input, $this->output]));
}
}
+225
View File
@@ -0,0 +1,225 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Command\Turn;
use OCA\Talk\Command\Turn\Delete;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class DeleteTest extends TestCase {
protected IConfig&MockObject $config;
protected InputInterface&MockObject $input;
protected OutputInterface&MockObject $output;
protected Delete $command;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->command = new Delete($this->config);
$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}
public function testDeleteIfEmpty(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn.example.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn('');
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('turn_servers'),
$this->equalTo(json_encode([]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>There is nothing to delete.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testDelete(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn2.example.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([
[
'schemes' => 'turn,turns',
'server' => 'turn1.example.com',
'secret' => 'my-test-secret-1',
'protocols' => 'udp,tcp'
]
]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('turn_servers'),
$this->equalTo(json_encode([
[
'schemes' => 'turn,turns',
'server' => 'turn1.example.com',
'secret' => 'my-test-secret-1',
'protocols' => 'udp,tcp'
]
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>There is nothing to delete.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testNothingToDelete(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn4.example.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([
[
'schemes' => 'turn,turns',
'server' => 'turn1.example.com',
'secret' => 'my-test-secret-1',
'protocols' => 'udp,tcp'
],
[
'schemes' => 'turn,turns',
'server' => 'turn2.example.com',
'secret' => 'my-test-secret-2',
'protocols' => 'udp,tcp'
],
[
'schemes' => 'turn,turns',
'server' => 'turn3.example.com',
'secret' => 'my-test-secret-3',
'protocols' => 'udp,tcp'
],
]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('turn_servers'),
$this->equalTo(json_encode([
[
'schemes' => 'turn,turns',
'server' => 'turn1.example.com',
'secret' => 'my-test-secret-1',
'protocols' => 'udp,tcp'
],
[
'schemes' => 'turn,turns',
'server' => 'turn2.example.com',
'secret' => 'my-test-secret-2',
'protocols' => 'udp,tcp'
],
[
'schemes' => 'turn,turns',
'server' => 'turn3.example.com',
'secret' => 'my-test-secret-3',
'protocols' => 'udp,tcp'
],
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>There is nothing to delete.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testDeleteMatchingSchemes(): void {
$this->input->method('getArgument')
->willReturnCallback(function ($arg) {
if ($arg === 'schemes') {
return 'turn,turns';
} elseif ($arg === 'server') {
return 'turn.example.com';
} elseif ($arg === 'protocols') {
return 'udp,tcp';
}
throw new \Exception();
});
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([
[
'schemes' => 'turn,turns',
'server' => 'turn.example.com',
'secret' => 'my-test-secret-1',
'protocols' => 'udp,tcp'
],
[
'schemes' => 'turn',
'server' => 'turn.example.com',
'secret' => 'my-test-secret-1',
'protocols' => 'udp,tcp'
]
]));
$this->config->expects($this->once())
->method('setAppValue')
->with(
$this->equalTo('spreed'),
$this->equalTo('turn_servers'),
$this->equalTo(json_encode([
[
'schemes' => 'turn',
'server' => 'turn.example.com',
'secret' => 'my-test-secret-1',
'protocols' => 'udp,tcp'
]
]))
);
$this->output->expects($this->once())
->method('writeln')
->with($this->equalTo('<info>Deleted turn.example.com.</info>'));
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
}
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Command\Turn;
use OCA\Talk\Command\Turn\ListCommand;
use OCP\IConfig;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Test\TestCase;
class ListCommandTest extends TestCase {
protected IConfig&MockObject $config;
protected InputInterface&MockObject $input;
protected OutputInterface&MockObject $output;
protected ListCommand&MockObject $command;
public function setUp(): void {
parent::setUp();
$this->config = $this->createMock(IConfig::class);
$this->command = $this->getMockBuilder(ListCommand::class)
->setConstructorArgs([$this->config])
->onlyMethods(['writeMixedInOutputFormat'])
->getMock();
$this->input = $this->createMock(InputInterface::class);
$this->output = $this->createMock(OutputInterface::class);
}
public function testEmptyAppConfig(): void {
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([]));
$this->command->expects($this->once())
->method('writeMixedInOutputFormat')
->with(
$this->equalTo($this->input),
$this->equalTo($this->output),
$this->equalTo([])
);
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
public function testAppConfigDataChanges(): void {
$this->config->expects($this->once())
->method('getAppValue')
->with('spreed', 'turn_servers')
->willReturn(json_encode([
[
'server' => 'turn1.test.com',
'secret' => 'my-sercret-1',
'protocols' => 'tcp',
],
[
'server' => 'turn2.test.com',
'secret' => 'my-sercret-2',
'protocols' => 'udp,tcp',
],
]));
$this->command->expects($this->once())
->method('writeMixedInOutputFormat')
->with(
$this->equalTo($this->input),
$this->equalTo($this->output),
$this->equalTo([
[
'server' => 'turn1.test.com',
'secret' => 'my-sercret-1',
'protocols' => 'tcp',
],
[
'server' => 'turn2.test.com',
'secret' => 'my-sercret-2',
'protocols' => 'udp,tcp',
],
])
);
self::invokePrivate($this->command, 'execute', [$this->input, $this->output]);
}
}
+479
View File
@@ -0,0 +1,479 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php;
use OCA\Talk\Config;
use OCA\Talk\Events\BeforeTurnServersGetEvent;
use OCA\Talk\Tests\php\Mocks\GetTurnServerListener;
use OCA\Talk\Vendor\Firebase\JWT\JWT;
use OCA\Talk\Vendor\Firebase\JWT\Key;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Config\IUserConfig;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class ConfigTest extends TestCase {
private function createConfig(IConfig $config) {
/** @var MockObject|IAppConfig $appConfig */
$appConfig = $this->createMock(IAppConfig::class);
/** @var MockObject|IUserConfig $appConfig */
$userConfig = $this->createMock(IUserConfig::class);
/** @var MockObject|ITimeFactory $timeFactory */
$timeFactory = $this->createMock(ITimeFactory::class);
/** @var MockObject|ISecureRandom $secureRandom */
$secureRandom = $this->createMock(ISecureRandom::class);
/** @var MockObject|IGroupManager $groupManager */
$groupManager = $this->createMock(IGroupManager::class);
/** @var MockObject|IUserManager $userManager */
$userManager = $this->createMock(IUserManager::class);
/** @var MockObject|IURLGenerator $urlGenerator */
$urlGenerator = $this->createMock(IURLGenerator::class);
/** @var MockObject|IEventDispatcher $dispatcher */
$dispatcher = $this->createMock(IEventDispatcher::class);
$helper = new Config($config, $appConfig, $userConfig, $secureRandom, $groupManager, $userManager, $urlGenerator, $timeFactory, $dispatcher);
return $helper;
}
public function testGetStunServers(): void {
$servers = [
'stun1.example.com:443',
'stun2.example.com:129',
];
/** @var MockObject|IConfig $config */
$config = $this->createMock(IConfig::class);
$config
->expects($this->once())
->method('getAppValue')
->with('spreed', 'stun_servers', json_encode(['stun.nextcloud.com:443']))
->willReturn(json_encode($servers));
$config
->expects($this->once())
->method('getSystemValueBool')
->with('has_internet_connection', true)
->willReturn(true);
$helper = $this->createConfig($config);
$this->assertSame($helper->getStunServers(), $servers);
}
public function testGetDefaultStunServer(): void {
/** @var MockObject|IConfig $config */
$config = $this->createMock(IConfig::class);
$config
->expects($this->once())
->method('getAppValue')
->with('spreed', 'stun_servers', json_encode(['stun.nextcloud.com:443']))
->willReturn(json_encode([]));
$config
->expects($this->once())
->method('getSystemValueBool')
->with('has_internet_connection', true)
->willReturn(true);
$helper = $this->createConfig($config);
$this->assertSame(['stun.nextcloud.com:443'], $helper->getStunServers());
}
public function testGetDefaultStunServerNoInternet(): void {
/** @var MockObject|IConfig $config */
$config = $this->createMock(IConfig::class);
$config
->expects($this->once())
->method('getAppValue')
->with('spreed', 'stun_servers', json_encode(['stun.nextcloud.com:443']))
->willReturn(json_encode([]));
$config
->expects($this->once())
->method('getSystemValueBool')
->with('has_internet_connection', true)
->willReturn(false);
$helper = $this->createConfig($config);
$this->assertSame([], $helper->getStunServers());
}
public function testGenerateTurnSettings(): void {
/** @var MockObject|IConfig $config */
$config = $this->createMock(IConfig::class);
$config
->expects($this->once())
->method('getAppValue')
->with('spreed', 'turn_servers', '')
->willReturn(json_encode([
[
// No scheme explicitly given
'server' => 'turn.example.org:3478',
'secret' => 'thisisasupersecretsecret',
'protocols' => 'udp,tcp',
],
[
'schemes' => 'turn,turns',
'server' => 'turn2.example.com:5349',
'secret' => 'ThisIsAlsoSuperSecret',
'protocols' => 'udp',
],
[
'schemes' => 'turns',
'server' => 'turn-tls.example.com:443',
'secret' => 'ThisIsAlsoSuperSecret',
'protocols' => 'tcp',
],
]));
/** @var MockObject|ITimeFactory $timeFactory */
$timeFactory = $this->createMock(ITimeFactory::class);
$timeFactory
->expects($this->once())
->method('getTime')
->willReturn(1479743025);
/** @var MockObject|IAppConfig $appConfig */
$appConfig = $this->createMock(IAppConfig::class);
/** @var MockObject|IUserConfig $appConfig */
$userConfig = $this->createMock(IUserConfig::class);
/** @var MockObject|IGroupManager $groupManager */
$groupManager = $this->createMock(IGroupManager::class);
/** @var MockObject|IUserManager $userManager */
$userManager = $this->createMock(IUserManager::class);
/** @var MockObject|IURLGenerator $urlGenerator */
$urlGenerator = $this->createMock(IURLGenerator::class);
/** @var MockObject|IEventDispatcher $dispatcher */
$dispatcher = $this->createMock(IEventDispatcher::class);
/** @var MockObject|ISecureRandom $secureRandom */
$secureRandom = $this->createMock(ISecureRandom::class);
$secureRandom
->expects($this->once())
->method('generate')
->with(16)
->willReturn('abcdefghijklmnop');
$helper = new Config($config, $appConfig, $userConfig, $secureRandom, $groupManager, $userManager, $urlGenerator, $timeFactory, $dispatcher);
//
$settings = $helper->getTurnSettings();
$this->assertEquals(3, count($settings));
$this->assertSame([
'schemes' => 'turn',
'server' => 'turn.example.org:3478',
'username' => '1479829425:abcdefghijklmnop',
'password' => '4VJLVbihLzuxgMfDrm5C3zy8kLQ=',
'protocols' => 'udp,tcp',
], $settings[0]);
$this->assertSame([
'schemes' => 'turn,turns',
'server' => 'turn2.example.com:5349',
'username' => '1479829425:abcdefghijklmnop',
'password' => 'Ol9DEqnvyN4g+IAM+vFnqhfWUTE=',
'protocols' => 'udp',
], $settings[1]);
$this->assertSame([
'schemes' => 'turns',
'server' => 'turn-tls.example.com:443',
'username' => '1479829425:abcdefghijklmnop',
'password' => 'Ol9DEqnvyN4g+IAM+vFnqhfWUTE=',
'protocols' => 'tcp',
], $settings[2]);
}
public function testGenerateTurnSettingsEmpty(): void {
/** @var MockObject|IConfig $config */
$config = $this->createMock(IConfig::class);
$config
->expects($this->once())
->method('getAppValue')
->with('spreed', 'turn_servers', '')
->willReturn(json_encode([]));
$helper = $this->createConfig($config);
$settings = $helper->getTurnSettings();
$this->assertEquals(0, count($settings));
}
public function testGenerateTurnSettingsEvent(): void {
/** @var MockObject|IConfig $config */
$config = $this->createMock(IConfig::class);
$config
->expects($this->once())
->method('getAppValue')
->with('spreed', 'turn_servers', '')
->willReturn(json_encode([]));
/** @var MockObject|IAppConfig $appConfig */
$appConfig = $this->createMock(IAppConfig::class);
/** @var MockObject|IUserConfig $appConfig */
$userConfig = $this->createMock(IUserConfig::class);
/** @var MockObject|ITimeFactory $timeFactory */
$timeFactory = $this->createMock(ITimeFactory::class);
/** @var MockObject|IGroupManager $groupManager */
$groupManager = $this->createMock(IGroupManager::class);
/** @var MockObject|IUserManager $userManager */
$userManager = $this->createMock(IUserManager::class);
/** @var MockObject|IURLGenerator $urlGenerator */
$urlGenerator = $this->createMock(IURLGenerator::class);
/** @var MockObject|ISecureRandom $secureRandom */
$secureRandom = $this->createMock(ISecureRandom::class);
/** @var IEventDispatcher $dispatcher */
$dispatcher = \OCP\Server::get(IEventDispatcher::class);
$servers = [
[
'schemes' => 'turn',
'server' => 'turn.domain.invalid',
'username' => 'john',
'password' => 'abcde',
'protocols' => 'udp,tcp',
],
[
'schemes' => 'turns',
'server' => 'turns.domain.invalid',
'username' => 'jane',
'password' => 'ABCDE',
'protocols' => 'tcp',
],
];
$dispatcher->addServiceListener(BeforeTurnServersGetEvent::class, GetTurnServerListener::class);
$helper = new Config($config, $appConfig, $userConfig, $secureRandom, $groupManager, $userManager, $urlGenerator, $timeFactory, $dispatcher);
$settings = $helper->getTurnSettings();
$this->assertSame($servers, $settings);
}
public static function dataGetWebSocketDomainForSignalingServer(): array {
return [
['http://blabla.nextcloud.com', 'ws://blabla.nextcloud.com'],
['http://blabla.nextcloud.com/', 'ws://blabla.nextcloud.com'],
['http://blabla.nextcloud.com/signaling', 'ws://blabla.nextcloud.com'],
['http://blabla.nextcloud.com/signaling/', 'ws://blabla.nextcloud.com'],
['http://blabla.nextcloud.com:80', 'ws://blabla.nextcloud.com:80'],
['http://blabla.nextcloud.com:80/', 'ws://blabla.nextcloud.com:80'],
['http://blabla.nextcloud.com:80/signaling', 'ws://blabla.nextcloud.com:80'],
['http://blabla.nextcloud.com:80/signaling/', 'ws://blabla.nextcloud.com:80'],
['http://blabla.nextcloud.com:8000', 'ws://blabla.nextcloud.com:8000'],
['http://blabla.nextcloud.com:8000/', 'ws://blabla.nextcloud.com:8000'],
['http://blabla.nextcloud.com:8000/signaling', 'ws://blabla.nextcloud.com:8000'],
['http://blabla.nextcloud.com:8000/signaling/', 'ws://blabla.nextcloud.com:8000'],
['https://blabla.nextcloud.com', 'wss://blabla.nextcloud.com'],
['https://blabla.nextcloud.com/', 'wss://blabla.nextcloud.com'],
['https://blabla.nextcloud.com/signaling', 'wss://blabla.nextcloud.com'],
['https://blabla.nextcloud.com/signaling/', 'wss://blabla.nextcloud.com'],
['https://blabla.nextcloud.com:443', 'wss://blabla.nextcloud.com:443'],
['https://blabla.nextcloud.com:443/', 'wss://blabla.nextcloud.com:443'],
['https://blabla.nextcloud.com:443/signaling', 'wss://blabla.nextcloud.com:443'],
['https://blabla.nextcloud.com:443/signaling/', 'wss://blabla.nextcloud.com:443'],
['https://blabla.nextcloud.com:8443', 'wss://blabla.nextcloud.com:8443'],
['https://blabla.nextcloud.com:8443/', 'wss://blabla.nextcloud.com:8443'],
['https://blabla.nextcloud.com:8443/signaling', 'wss://blabla.nextcloud.com:8443'],
['https://blabla.nextcloud.com:8443/signaling/', 'wss://blabla.nextcloud.com:8443'],
['ws://blabla.nextcloud.com', 'ws://blabla.nextcloud.com'],
['ws://blabla.nextcloud.com/', 'ws://blabla.nextcloud.com'],
['ws://blabla.nextcloud.com/signaling', 'ws://blabla.nextcloud.com'],
['ws://blabla.nextcloud.com/signaling/', 'ws://blabla.nextcloud.com'],
['ws://blabla.nextcloud.com:80', 'ws://blabla.nextcloud.com:80'],
['ws://blabla.nextcloud.com:80/', 'ws://blabla.nextcloud.com:80'],
['ws://blabla.nextcloud.com:80/signaling', 'ws://blabla.nextcloud.com:80'],
['ws://blabla.nextcloud.com:80/signaling/', 'ws://blabla.nextcloud.com:80'],
['ws://blabla.nextcloud.com:8000', 'ws://blabla.nextcloud.com:8000'],
['ws://blabla.nextcloud.com:8000/', 'ws://blabla.nextcloud.com:8000'],
['ws://blabla.nextcloud.com:8000/signaling', 'ws://blabla.nextcloud.com:8000'],
['ws://blabla.nextcloud.com:8000/signaling/', 'ws://blabla.nextcloud.com:8000'],
['wss://blabla.nextcloud.com', 'wss://blabla.nextcloud.com'],
['wss://blabla.nextcloud.com/', 'wss://blabla.nextcloud.com'],
['wss://blabla.nextcloud.com/signaling', 'wss://blabla.nextcloud.com'],
['wss://blabla.nextcloud.com/signaling/', 'wss://blabla.nextcloud.com'],
['wss://blabla.nextcloud.com:443', 'wss://blabla.nextcloud.com:443'],
['wss://blabla.nextcloud.com:443/', 'wss://blabla.nextcloud.com:443'],
['wss://blabla.nextcloud.com:443/signaling', 'wss://blabla.nextcloud.com:443'],
['wss://blabla.nextcloud.com:443/signaling/', 'wss://blabla.nextcloud.com:443'],
['wss://blabla.nextcloud.com:8443', 'wss://blabla.nextcloud.com:8443'],
['wss://blabla.nextcloud.com:8443/', 'wss://blabla.nextcloud.com:8443'],
['wss://blabla.nextcloud.com:8443/signaling', 'wss://blabla.nextcloud.com:8443'],
['wss://blabla.nextcloud.com:8443/signaling/', 'wss://blabla.nextcloud.com:8443'],
// Admin got interrupted before finishing typing
['wss://', ''],
['ws://', ''],
['https://', ''],
['http://', ''],
['wss:/', ''],
['https:/', ''],
['wss:', ''],
['https:', ''],
['wss', 'wss'],
['https', 'https'],
['ws', 'ws'],
['http', 'http'],
['w', 'w'],
['htt', 'htt'],
['ht', 'ht'],
['h', 'h'],
];
}
/**
* @param string $url
* @param string $expectedWebSocketDomain
*/
#[DataProvider('dataGetWebSocketDomainForSignalingServer')]
public function testGetWebSocketDomainForSignalingServer($url, $expectedWebSocketDomain): void {
/** @var MockObject|IConfig $config */
$config = $this->createMock(IConfig::class);
$helper = $this->createConfig($config);
$this->assertEquals(
$expectedWebSocketDomain,
self::invokePrivate($helper, 'getWebSocketDomainForSignalingServer', [$url])
);
}
public static function dataTicketV2Algorithm(): array {
return [
['ES384'],
['ES256'],
['RS256'],
['RS384'],
['RS512'],
['EdDSA'],
];
}
#[DataProvider('dataTicketV2Algorithm')]
public function testSignalingTicketV2User(string $algo): void {
$config = \OCP\Server::get(IConfig::class);
/** @var MockObject|IAppConfig $appConfig */
$appConfig = $this->createMock(IAppConfig::class);
/** @var MockObject|IUserConfig $appConfig */
$userConfig = $this->createMock(IUserConfig::class);
/** @var MockObject|ITimeFactory $timeFactory */
$timeFactory = $this->createMock(ITimeFactory::class);
/** @var MockObject|ISecureRandom $secureRandom */
$secureRandom = $this->createMock(ISecureRandom::class);
/** @var MockObject|IGroupManager $groupManager */
$groupManager = $this->createMock(IGroupManager::class);
/** @var MockObject|IUserManager $userManager */
$userManager = $this->createMock(IUserManager::class);
/** @var MockObject|IURLGenerator $urlGenerator */
$urlGenerator = $this->createMock(IURLGenerator::class);
/** @var MockObject|IEventDispatcher $dispatcher */
$dispatcher = $this->createMock(IEventDispatcher::class);
/** @var MockObject|IUser $user */
$user = $this->createMock(IUser::class);
$now = time();
$timeFactory
->expects($this->once())
->method('getTime')
->willReturn($now);
$urlGenerator
->expects($this->once())
->method('getAbsoluteURL')
->with('')
->willReturn('https://domain.invalid/nextcloud');
$userManager
->expects($this->once())
->method('get')
->with('user1')
->willReturn($user);
$user
->expects($this->once())
->method('getUID')
->willReturn('user1');
$user
->expects($this->once())
->method('getDisplayName')
->willReturn('Jane Doe');
$helper = new Config($config, $appConfig, $userConfig, $secureRandom, $groupManager, $userManager, $urlGenerator, $timeFactory, $dispatcher);
$config->setAppValue('spreed', 'signaling_token_alg', $algo);
// Make sure new keys are generated.
$config->deleteAppValue('spreed', 'signaling_token_privkey_' . strtolower($algo));
$config->deleteAppValue('spreed', 'signaling_token_pubkey_' . strtolower($algo));
$ticket = $helper->getSignalingTicket(Config::SIGNALING_TICKET_V2, 'user1');
$this->assertNotNull($ticket);
$key = new Key($config->getAppValue('spreed', 'signaling_token_pubkey_' . strtolower($algo)), $algo);
$decoded = JWT::decode($ticket, $key);
$this->assertEquals($now, $decoded->iat);
$this->assertEquals('https://domain.invalid/nextcloud', $decoded->iss);
$this->assertEquals('user1', $decoded->sub);
$this->assertSame(['displayname' => 'Jane Doe'], (array)$decoded->userdata);
}
#[DataProvider('dataTicketV2Algorithm')]
public function testSignalingTicketV2Anonymous(string $algo): void {
/** @var IConfig $config */
$config = \OCP\Server::get(IConfig::class);
/** @var MockObject|IAppConfig $appConfig */
$appConfig = $this->createMock(IAppConfig::class);
/** @var MockObject|IUserConfig $appConfig */
$userConfig = $this->createMock(IUserConfig::class);
/** @var MockObject|ITimeFactory $timeFactory */
$timeFactory = $this->createMock(ITimeFactory::class);
/** @var MockObject|ISecureRandom $secureRandom */
$secureRandom = $this->createMock(ISecureRandom::class);
/** @var MockObject|IGroupManager $groupManager */
$groupManager = $this->createMock(IGroupManager::class);
/** @var MockObject|IUserManager $userManager */
$userManager = $this->createMock(IUserManager::class);
/** @var MockObject|IURLGenerator $urlGenerator */
$urlGenerator = $this->createMock(IURLGenerator::class);
/** @var MockObject|IEventDispatcher $dispatcher */
$dispatcher = $this->createMock(IEventDispatcher::class);
$now = time();
$timeFactory
->expects($this->once())
->method('getTime')
->willReturn($now);
$urlGenerator
->expects($this->once())
->method('getAbsoluteURL')
->with('')
->willReturn('https://domain.invalid/nextcloud');
$helper = new Config($config, $appConfig, $userConfig, $secureRandom, $groupManager, $userManager, $urlGenerator, $timeFactory, $dispatcher);
$config->setAppValue('spreed', 'signaling_token_alg', $algo);
// Make sure new keys are generated.
$config->deleteAppValue('spreed', 'signaling_token_privkey_' . strtolower($algo));
$config->deleteAppValue('spreed', 'signaling_token_pubkey_' . strtolower($algo));
$ticket = $helper->getSignalingTicket(Config::SIGNALING_TICKET_V2, null);
$this->assertNotNull($ticket);
$key = new Key($config->getAppValue('spreed', 'signaling_token_pubkey_' . strtolower($algo)), $algo);
$decoded = JWT::decode($ticket, $key);
$this->assertEquals($now, $decoded->iat);
$this->assertEquals('https://domain.invalid/nextcloud', $decoded->iss);
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+53
View File
@@ -0,0 +1,53 @@
<?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\Unit;
use PHPUnit\Framework\Attributes\DataProvider;
use Test\TestCase;
class EventDocumentationTest extends TestCase {
public static function dataEventDocumentation(): array {
$dir = new \DirectoryIterator(__DIR__ . '/../../lib/Events');
$data = [];
foreach ($dir as $fileinfo) {
if (!$fileinfo->isDot()) {
$data[] = ['OCA\\Talk\\Events\\' . substr($fileinfo->getFilename(), 0, -4)];
}
}
sort($data);
return $data;
}
#[DataProvider('dataEventDocumentation')]
public function testEventDocumentation(string $eventClass): void {
$reflectionClass = new \ReflectionClass($eventClass);
if ($reflectionClass->isAbstract()) {
self::assertTrue(true, 'Abstract event class ' . $eventClass . ' does not have to be documented');
return;
}
$classDocBlock = $reflectionClass->getDocComment();
if (is_string($classDocBlock) && str_contains($classDocBlock, '@deprecated')) {
self::assertTrue(true, 'Deprecated event ' . $eventClass . ' does not have to be documented');
return;
}
if (is_string($classDocBlock) && str_contains($classDocBlock, '@internal')) {
self::assertTrue(true, 'Internal event ' . $eventClass . ' does not have to be documented');
return;
}
$docs = file_get_contents(__DIR__ . '/../../docs/events.md');
$eventIsDocumented = str_contains($docs, 'Before event: `' . $eventClass . '`')
|| str_contains($docs, 'After event: `' . $eventClass . '`')
|| str_contains($docs, 'Final event: `' . $eventClass . '`')
|| str_contains($docs, 'Event: `' . $eventClass . '`');
self::assertTrue($eventIsDocumented, 'Asserting that event ' . $eventClass . ' is documented');
}
}
+463
View File
@@ -0,0 +1,463 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Federation;
use OC\Federation\CloudFederationShare;
use OCA\FederatedFileSharing\AddressHandler;
use OCA\Talk\Config;
use OCA\Talk\Federation\BackendNotifier;
use OCA\Talk\Federation\CloudFederationProviderTalk;
use OCA\Talk\Federation\FederationManager;
use OCA\Talk\Federation\Proxy\TalkV1\UserConverter;
use OCA\Talk\Federation\RestrictionValidator;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\AttendeeMapper;
use OCA\Talk\Model\Invitation;
use OCA\Talk\Model\InvitationMapper;
use OCA\Talk\Model\ProxyCacheMessageMapper;
use OCA\Talk\Model\RetryNotificationMapper;
use OCA\Talk\Notification\FederationChatNotifier;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\ProxyCacheMessageService;
use OCA\Talk\Service\RoomService;
use OCP\App\IAppManager;
use OCP\AppFramework\Http;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Federation\ICloudFederationFactory;
use OCP\Federation\ICloudFederationNotification;
use OCP\Federation\ICloudFederationProviderManager;
use OCP\Federation\ICloudFederationShare;
use OCP\Federation\ICloudId;
use OCP\Federation\ICloudIdManager;
use OCP\Http\Client\IResponse;
use OCP\ICacheFactory;
use OCP\ISession;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Notification\IManager as INotificationManager;
use OCP\Notification\INotification;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class FederationTest extends TestCase {
protected FederationManager&MockObject $federationManager;
protected ICloudIdManager&MockObject $cloudIdManager;
protected ICloudFederationProviderManager&MockObject $cloudFederationProviderManager;
protected ICloudFederationFactory&MockObject $cloudFederationFactory;
protected Config&MockObject $config;
protected IAppConfig&MockObject $appConfig;
protected LoggerInterface&MockObject $logger;
protected AddressHandler&MockObject $addressHandler;
protected IUserManager&MockObject $userManager;
protected IAppManager&MockObject $appManager;
protected IURLGenerator&MockObject $url;
protected INotificationManager&MockObject $notificationManager;
protected AttendeeMapper&MockObject $attendeeMapper;
protected ProxyCacheMessageMapper&MockObject $proxyCacheMessageMapper;
protected ProxyCacheMessageService&MockObject $proxyCacheMessageService;
protected FederationChatNotifier&MockObject $federationChatNotifier;
protected UserConverter&MockObject $userConverter;
protected ICacheFactory&MockObject $cacheFactory;
protected RetryNotificationMapper&MockObject $retryNotificationMapper;
protected ITimeFactory&MockObject $timeFactory;
protected RestrictionValidator&MockObject $restrictionValidator;
protected ?CloudFederationProviderTalk $cloudFederationProvider = null;
protected ?BackendNotifier $backendNotifier = null;
public function setUp(): void {
parent::setUp();
$this->cloudIdManager = $this->createMock(ICloudIdManager::class);
$this->cloudFederationProviderManager = $this->createMock(ICloudFederationProviderManager::class);
$this->cloudFederationFactory = $this->createMock(ICloudFederationFactory::class);
$this->addressHandler = $this->createMock(AddressHandler::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->attendeeMapper = $this->createMock(AttendeeMapper::class);
$this->config = $this->createMock(Config::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->appManager = $this->createMock(IAppManager::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->url = $this->createMock(IURLGenerator::class);
$this->proxyCacheMessageMapper = $this->createMock(ProxyCacheMessageMapper::class);
$this->proxyCacheMessageService = $this->createMock(ProxyCacheMessageService::class);
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->retryNotificationMapper = $this->createMock(RetryNotificationMapper::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->restrictionValidator = $this->createMock(RestrictionValidator::class);
$this->backendNotifier = new BackendNotifier(
$this->cloudFederationFactory,
$this->addressHandler,
$this->logger,
$this->cloudFederationProviderManager,
$this->userManager,
$this->url,
$this->retryNotificationMapper,
$this->timeFactory,
$this->cloudIdManager,
$this->restrictionValidator,
);
$this->federationManager = $this->createMock(FederationManager::class);
$this->notificationManager = $this->createMock(INotificationManager::class);
$this->federationChatNotifier = $this->createMock(FederationChatNotifier::class);
$this->userConverter = $this->createMock(UserConverter::class);
$this->cloudFederationProvider = new CloudFederationProviderTalk(
$this->cloudIdManager,
$this->userManager,
$this->addressHandler,
$this->federationManager,
$this->config,
$this->appConfig,
$this->notificationManager,
$this->createMock(ParticipantService::class),
$this->createMock(RoomService::class),
$this->attendeeMapper,
$this->createMock(InvitationMapper::class),
$this->createMock(Manager::class),
$this->createMock(ISession::class),
$this->createMock(IEventDispatcher::class),
$this->logger,
$this->proxyCacheMessageMapper,
$this->proxyCacheMessageService,
$this->federationChatNotifier,
$this->userConverter,
$this->timeFactory,
$this->cacheFactory,
);
}
public function testSendRemoteShareWithOwner(): void {
$cloudShare = $this->createMock(ICloudFederationShare::class);
$providerId = '3';
$token = 'abcdefghijklmno';
$shareWith = 'test@remote.test.local';
$name = 'abcdefgh';
$owner = 'Owner\'s name';
$ownerId = 'owner';
$ownerFederatedId = $ownerId . '@test.local';
$sharedByDisplayName = 'Owner\'s name';
$sharedByFederatedId = 'owner@test.local';
$shareType = 'user';
$roomType = Room::TYPE_GROUP;
$roomName = 'Room name';
$room = $this->createMock(Room::class);
$attendee = $this->createStub(Attendee::class);
$ownerUser = $this->createMock(IUser::class);
$sharedBy = $this->createMock(IUser::class);
$sharedBy->expects($this->once())
->method('getCloudId')
->with()
->willReturn($sharedByFederatedId);
$sharedBy->expects($this->once())
->method('getDisplayName')
->with()
->willReturn($sharedByDisplayName);
$room->expects($this->once())
->method('getName')
->with()
->willReturn($roomName);
$room->expects($this->once())
->method('getType')
->with()
->willReturn($roomType);
$room->expects($this->once())
->method('getToken')
->with()
->willReturn($name);
$this->userManager->expects($this->once())
->method('get')
->willReturn($ownerUser);
$ownerUser->expects($this->once())
->method('getCloudId')
->with()
->willReturn($ownerFederatedId);
$ownerUser->expects($this->once())
->method('getDisplayName')
->with()
->willReturn($owner);
$this->cloudFederationFactory->expects($this->once())
->method('getCloudFederationShare')
->with(
$shareWith,
$name,
'',
$providerId,
$ownerFederatedId,
$owner,
$sharedByFederatedId,
$sharedByDisplayName,
$token,
$shareType,
'talk-room'
)
->willReturn($cloudShare);
$this->cloudFederationProviderManager->expects($this->once())
->method('sendCloudShare')
->with($cloudShare);
$cloudId = $this->createMock(ICloudId::class);
$cloudId->method('getRemote')
->willReturn('remote.test.local');
$cloudId->method('getUser')
->willReturn('test');
$this->cloudIdManager->expects($this->once())
->method('resolveCloudId')
->with($shareWith)
->willReturn($cloudId);
$this->appConfig->method('getAppValueBool')
->willReturnMap([
['federation_outgoing_enabled', true, false, true],
['federation_only_trusted_servers', false, false, false],
]);
$this->config->method('isFederationEnabledForUserId')
->with($sharedBy)
->willReturn(true);
$this->backendNotifier->sendRemoteShare($providerId, $token, $shareWith, $sharedBy, $shareType, $room, $attendee);
}
public function testReceiveRemoteShare(): void {
$providerId = '3';
$token = 'abcdefghijklmno';
$shareWith = 'test@remote.test.local';
$name = 'abcdefgh';
$owner = 'Owner\'s name';
$ownerFederatedId = 'owner@test.local';
$sharedBy = 'Owner\'s name';
$sharedByFederatedId = 'owner@test.local';
$remote = 'https://test.local';
$shareType = 'user';
$roomType = Room::TYPE_GROUP;
$roomName = 'Room name';
$roomDefaultPermissions = Attendee::PERMISSIONS_CUSTOM | Attendee::PERMISSIONS_CHAT;
$shareWithUser = $this->createMock(IUser::class);
$shareWithUserID = '10';
$share = new CloudFederationShare(
$shareWith,
$name,
'',
$providerId,
$ownerFederatedId,
$owner,
$sharedByFederatedId,
$sharedBy,
$shareType,
'talk-room',
$token
);
$share->setProtocol([
'name' => 'nctalk',
'roomType' => $roomType,
'roomName' => $roomName,
'roomDefaultPermissions' => $roomDefaultPermissions,
'options' => [
'sharedSecret' => $token,
],
'invitedCloudId' => 'test@remote.test.local',
]);
$invite = Invitation::fromRow(['id' => 20]);
// Test receiving federation expectations
$this->federationManager->expects($this->once())
->method('addRemoteRoom')
->with($shareWithUser, $providerId, $roomType, $roomName, $roomDefaultPermissions, $name, $remote, $token)
->willReturn($invite);
$this->config->method('isFederationEnabled')
->willReturn(true);
$this->appConfig->method('getAppValueBool')
->willReturnMap([
['federation_incoming_enabled', true, false, true],
]);
$this->config->method('isDisabledForUser')
->with($shareWithUser)
->willReturn(false);
$this->config->method('isFederationEnabledForUserId')
->with($shareWithUser)
->willReturn(true);
$this->addressHandler->expects($this->once())
->method('splitUserRemote')
->with($ownerFederatedId)
->willReturn(['owner', $remote]);
$this->addressHandler->expects($this->once())
->method('urlContainProtocol')
->willReturnCallback(static fn (string $url) => str_starts_with($url, 'http://') || str_starts_with($url, 'https://'));
$this->userManager->expects($this->once())
->method('get')
->with($shareWith)
->willReturn($shareWithUser);
// Test sending notification expectations
$shareWithUser->method('getUID')
->willReturn($shareWithUserID);
$notification = $this->createMock(INotification::class);
$notification->expects($this->once())
->method('setApp')
->willReturnSelf();
$notification->expects($this->once())
->method('setUser')
->with($shareWithUserID)
->willReturnSelf();
$notification->expects($this->once())
->method('setDateTime')
->willReturnSelf();
$notification->expects($this->once())
->method('setObject')
->with('remote_talk_share', 20)
->willReturnSelf();
$notification->expects($this->once())
->method('setSubject')
->with('remote_talk_share', [
'sharedByDisplayName' => $sharedBy,
'sharedByFederatedId' => $sharedByFederatedId,
'roomName' => $roomName,
'serverUrl' => $remote,
'roomToken' => $name,
]);
$this->notificationManager->expects($this->once())
->method('createNotification')
->with()
->willReturn($notification);
$this->notificationManager->expects($this->once())
->method('notify')
->with($notification);
$this->assertSame('20',
$this->cloudFederationProvider->shareReceived($share)
);
}
public function testSendAcceptNotification(): void {
$remote = 'https://remote.test.local';
$id = 50;
$token = 'abcdefghijklmno';
$notification = $this->createMock(ICloudFederationNotification::class);
$notification->expects($this->once())
->method('setMessage')
->with(
'SHARE_ACCEPTED',
FederationManager::TALK_ROOM_RESOURCE,
$id,
[
'sharedSecret' => $token,
'message' => 'Recipient accepted the share',
'remoteServerUrl' => 'http://example.tld',
'displayName' => 'Foo Bar',
'cloudId' => 'cloudId@example.tld',
]
);
$this->cloudFederationFactory->expects($this->once())
->method('getCloudFederationNotification')
->with()
->willReturn($notification);
$response = $this->createMock(IResponse::class);
$response->method('getStatusCode')
->willReturn(Http::STATUS_CREATED);
$this->cloudFederationProviderManager->expects($this->once())
->method('sendCloudNotification')
->with($remote, $notification)
->willReturn($response);
$this->addressHandler->method('urlContainProtocol')
->with($remote)
->willReturn(true);
$this->url->method('getAbsoluteURL')
->with('/')
->willReturn('http://example.tld/index.php/');
$success = $this->backendNotifier->sendShareAccepted($remote, $id, $token, 'Foo Bar', 'cloudId@example.tld');
$this->assertTrue($success);
}
public function testSendRejectNotification(): void {
$remote = 'https://remote.test.local';
$id = 50;
$token = 'abcdefghijklmno';
$notification = $this->createMock(ICloudFederationNotification::class);
$notification->expects($this->once())
->method('setMessage')
->with(
'SHARE_DECLINED',
FederationManager::TALK_ROOM_RESOURCE,
$id,
[
'sharedSecret' => $token,
'message' => 'Recipient declined the share',
'remoteServerUrl' => 'https://example.tld',
]
);
$this->cloudFederationFactory->expects($this->once())
->method('getCloudFederationNotification')
->with()
->willReturn($notification);
$response = $this->createMock(IResponse::class);
$response->method('getStatusCode')
->willReturn(Http::STATUS_CREATED);
$this->cloudFederationProviderManager->expects($this->once())
->method('sendCloudNotification')
->with($remote, $notification)
->willReturn($response);
$this->addressHandler->method('urlContainProtocol')
->with($remote)
->willReturn(true);
$this->url->method('getAbsoluteURL')
->with('/')
->willReturn('https://example.tld/index.php/');
$this->backendNotifier->sendShareDeclined($remote, $id, $token);
}
}
+152
View File
@@ -0,0 +1,152 @@
<?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;
use OCA\Talk\Config;
use OCA\Talk\Exceptions\GuestImportException;
use OCA\Talk\GuestManager;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\PollService;
use OCA\Talk\Service\RoomService;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IDateTimeZone;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUserSession;
use OCP\Mail\IMailer;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
class GuestManagerTest extends TestCase {
protected Config&MockObject $talkConfig;
protected IMailer&MockObject $mailer;
protected Defaults&MockObject $defaults;
protected IUserSession&MockObject $userSession;
protected ParticipantService&MockObject $participantService;
protected PollService&MockObject $pollService;
protected RoomService&MockObject $roomService;
protected IURLGenerator&MockObject $urlGenerator;
protected IL10N&MockObject $l;
protected IEventDispatcher&MockObject $dispatcher;
protected LoggerInterface&MockObject $logger;
private IDateTimeZone&MockObject $dateTime;
public function setUp(): void {
parent::setUp();
$this->talkConfig = $this->createMock(Config::class);
$this->mailer = $this->createMock(IMailer::class);
$this->defaults = $this->createMock(Defaults::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->pollService = $this->createMock(PollService::class);
$this->roomService = $this->createMock(RoomService::class);
$this->urlGenerator = $this->createMock(IURLGenerator::class);
$this->l = $this->createMock(IL10N::class);
$this->dispatcher = $this->createMock(IEventDispatcher::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->dateTime = $this->createMock(IDateTimeZone::class);
}
public function getGuestManager(array $methods = []): GuestManager|MockObject {
if (!empty($methods)) {
return $this->getMockBuilder(GuestManager::class)
->setConstructorArgs([
$this->talkConfig,
$this->mailer,
$this->defaults,
$this->userSession,
$this->participantService,
$this->pollService,
$this->roomService,
$this->urlGenerator,
$this->l,
$this->dispatcher,
$this->logger,
$this->dateTime,
])
->onlyMethods($methods)
->getMock();
}
$this->guestManager = new GuestManager(
$this->talkConfig,
$this->mailer,
$this->defaults,
$this->userSession,
$this->participantService,
$this->pollService,
$this->roomService,
$this->urlGenerator,
$this->l,
$this->dispatcher,
$this->logger,
$this->dateTime,
);
}
public static function dataImportEmails(): array {
return [
[
'import-valid-only-email.csv',
1,
0,
[['valid@example.tld', null]],
],
[
'import-valid-email-and-name.csv',
1,
0,
[['valid@example.tld', 'Name']],
],
[
'import-valid-filter-duplicates-by-email.csv',
2,
1,
[['valid-1@example.tld', 'Valid 1'], ['valid-2@example.tld',null]],
GuestImportException::REASON_ROWS,
[4],
],
];
}
#[DataProvider('dataImportEmails')]
public function testImportEmails(string $fileName, int $invites, int $duplicates, array $invited, ?string $reason = null, array $invalidLines = []): void {
$this->mailer->method('validateMailAddress')
->willReturnCallback(static fn (string $email): bool => str_starts_with($email, 'valid'));
$actualInvites = [];
$this->participantService->method('inviteEmailAddress')
->willReturnCallback(function ($room, $actorId, string $email, ?string $name) use (&$actualInvites): Participant {
$actualInvites[] = [$email, $name];
return $this->createMock(Participant::class);
});
$room = $this->createMock(Room::class);
try {
$guestManager = $this->getGuestManager(['sendEmailInvitation']);
$data = $guestManager->importEmails($room, __DIR__ . '/data/' . $fileName, false);
} catch (GuestImportException $e) {
if ($reason === null) {
throw $e;
}
$data = $e->getData();
$this->assertSame($invalidLines, $data['invalidLines']);
}
$this->assertSame($invited, $actualInvites);
$this->assertSame($invites, $data['invites'], 'Invites count mismatch');
$this->assertSame($duplicates, $data['duplicates'], 'Duplicates count mismatch');
}
}
@@ -0,0 +1,844 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Listener;
use OCA\DAV\CalDAV\TimezoneService;
use OCA\Talk\Events\ACallEndedEvent;
use OCA\Talk\Exceptions\ParticipantNotFoundException;
use OCA\Talk\Exceptions\RoomNotFoundException;
use OCA\Talk\Listener\CalDavEventListener;
use OCA\Talk\Manager;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RoomService;
use OCA\Talk\Webinary;
use OCP\Calendar\Events\CalendarObjectCreatedEvent;
use OCP\Calendar\Events\CalendarObjectDeletedEvent;
use OCP\Calendar\Events\CalendarObjectUpdatedEvent;
use OCP\IL10N;
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 CalDavEventListenerTest extends TestCase {
private Manager&MockObject $manager;
private RoomService&MockObject $roomService;
private LoggerInterface&MockObject $logger;
private TimezoneService&MockObject $timezoneService;
private ParticipantService&MockObject $participantService;
private string $calData;
private Participant&MockObject $participant;
private string $userId;
private string $userUri;
private IL10N&MockObject $l10n;
private CalDavEventListener $listener;
public static function roomUrl() {
return [
['http://talk.example.com/call/12345'],
['http://talk.example.com/call/12345#message_789456'],
['http://talk.example.com/call/12345#?message_789456'],
['http://talk.example.com/call/12345?email=test@example.tld'],
['http://talk.example.com/call/12345?email=test@example.tld#message_789456'],
['http://talk.example.com/call/12345?email=test@example.tld#message_789456?email=test@example.tld'],
];
}
public function setUp(): void {
parent::setUp();
$this->manager = $this->createMock(Manager::class);
$this->roomService = $this->createMock(RoomService::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->timezoneService = $this->createMock(TimezoneService::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->userId = '123';
$this->userUri = 'principals/users/' . $this->userId;
$this->l10n = $this->createMock(IL10N::class);
$this->calData = <<<EOD
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T171800Z
DTSTAMP:20250310T171819Z
LAST-MODIFIED:20250310T171819Z
SEQUENCE:2
UID:4d336aa1-a29e-4015-b1dd-98e1dae802db
DTSTART;TZID=Europe/Vienna:20250314T100000
DTEND;TZID=Europe/Vienna:20250314T110000
STATUS:CONFIRMED
SUMMARY:Test
LOCATION:{{{LOCATION}}}
END:VEVENT
BEGIN:VTIMEZONE
TZID:Europe/Vienna
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
END:VCALENDAR
EOD;
$attendee = new Attendee();
$attendee->setParticipantType(Participant::OWNER);
$this->participant = $this->createMock(Participant::class);
$this->participant->method('getAttendee')->willReturn($attendee);
$this->listener = new CalDavEventListener(
$this->manager,
$this->roomService,
$this->logger,
$this->timezoneService,
$this->participantService,
$this->l10n,
);
}
public function testIsNotCalendarEvent(): void {
$event = $this->createMock(ACallEndedEvent::class);
$this->manager->expects(self::never())
->method('getRoomForUserByToken');
$this->logger->expects(self::never())
->method('warning');
$this->logger->expects(self::never())
->method('debug');
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::never())
->method('setObject');
$this->participantService->expects(self::never())
->method('getParticipant');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->roomService->expects(self::never())
->method('hasExistingCalendarEvents');
$this->listener->handle($event);
}
public function testIsCalendarEventNoPrincipal(): void {
$event = new CalendarObjectCreatedEvent(1, [], [], ['calendardata' => 'justSomeData']);
$this->logger->expects(self::once())
->method('debug')
->with('No principal uri for the event, skipping for calendar event integration');
$this->listener->handle($event);
}
public function testIsCalendarEventSystemCalendar(): void {
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => 'principals/system/system'], [], ['calendardata' => 'justSomeData']);
$this->logger->expects(self::once())
->method('debug')
->with('System calendar, skipping for calendar event integration');
$this->listener->handle($event);
}
public function testIsCalendarEventNoEventInVObject(): void {
$calData = <<<EOD
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Something fancy//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VTIMEZONE
TZID:Europe/Paris
X-LIC-LOCATION:Europe/Paris
END:VTIMEZONE
BEGIN:VTODO
CREATED:20250310T171800Z
DTSTAMP:20250310T171819Z
LAST-MODIFIED:20250310T171819Z
UID:4d336aa1-a29e-4015-b1dd-98e1dae802db
DTSTART;TZID=Europe/Vienna:20250314T100000
DUE;TZID=Europe/Vienna:20250314T110000
STATUS:CONFIRMED
SUMMARY:Test
END:VTODO
END:VCALENDAR
EOD;
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$this->logger->expects(self::once())
->method('debug')
->with('Calendar object is not an event, skipping for calendar event integration');
$this->listener->handle($event);
}
public function testIsCalendarEventNoData(): void {
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], []);
$this->logger->expects(self::once())
->method('debug')
->with('No calendar data for the event, skipping for calendar event integration');
$this->listener->handle($event);
}
public function testIsCalendarEventNoLocation(): void {
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => 'justSomeData']);
$this->logger->expects(self::once())
->method('debug');
$this->manager->expects(self::never())
->method('getRoomForUserByToken');
$this->logger->expects(self::never())
->method('warning');
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::never())
->method('setObject');
$this->participantService->expects(self::never())
->method('getParticipant');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->roomService->expects(self::never())
->method('hasExistingCalendarEvents');
$this->listener->handle($event);
}
public function testIsCalendarEventInvalidCalendarData(): void {
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => 'justSomeData\nLOCATION:']);
$this->logger->expects(self::once())
->method('warning');
$this->manager->expects(self::never())
->method('getRoomForUserByToken');
$this->logger->expects(self::never())
->method('debug');
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::never())
->method('setObject');
$this->participantService->expects(self::never())
->method('getParticipant');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->roomService->expects(self::never())
->method('hasExistingCalendarEvents');
$this->listener->handle($event);
}
public function testNoUrlInLocation(): void {
$calData = <<<EOD
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T171800Z
DTSTAMP:20250310T171819Z
LAST-MODIFIED:20250310T171819Z
SEQUENCE:2
UID:4d336aa1-a29e-4015-b1dd-98e1dae802db
DTSTART;TZID=Europe/Vienna:20250314T100000
DTEND;TZID=Europe/Vienna:20250314T110000
STATUS:CONFIRMED
SUMMARY:Test
LOCATION:Donde esta la biblioteca
END:VEVENT
BEGIN:VTIMEZONE
TZID:Europe/Vienna
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
END:VCALENDAR
EOD;
$event = new CalendarObjectUpdatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$this->logger->expects(self::once())
->method('debug');
$this->manager->expects(self::never())
->method('getRoomForUserByToken');
$this->logger->expects(self::never())
->method('warning');
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::never())
->method('setObject');
$this->participantService->expects(self::never())
->method('getParticipant');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->roomService->expects(self::never())
->method('hasExistingCalendarEvents');
$this->listener->handle($event);
}
#[DataProvider('roomUrl')]
public function testRoomNotFound(string $roomUrl): void {
$calData = str_replace('{{{LOCATION}}}', $roomUrl, $this->calData);
$event = new CalendarObjectUpdatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willThrowException(new RoomNotFoundException());
$this->logger->expects(self::once())
->method('warning');
$this->logger->expects(self::never())
->method('debug');
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::never())
->method('setObject');
$this->participantService->expects(self::never())
->method('getParticipant');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->roomService->expects(self::never())
->method('hasExistingCalendarEvents');
$this->listener->handle($event);
}
#[DataProvider('roomUrl')]
public function testUserNotParticipant(string $roomUrl): void {
$calData = str_replace('{{{LOCATION}}}', $roomUrl, $this->calData);
$event = new CalendarObjectUpdatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$this->manager->expects(self::once())
->method('getRoomForUserByToken');
$this->participantService->expects(self::once())
->method('getParticipant')
->willThrowException(new ParticipantNotFoundException());
$this->logger->expects(self::never())
->method('warning');
$this->logger->expects(self::once())
->method('debug');
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::never())
->method('setObject');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->roomService->expects(self::never())
->method('hasExistingCalendarEvents');
$this->listener->handle($event);
}
#[DataProvider('roomUrl')]
public function testUserNotOwner(string $roomUrl): void {
$calData = str_replace('{{{LOCATION}}}', $roomUrl, $this->calData);
$event = new CalendarObjectUpdatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$attendee = new Attendee();
$attendee->setParticipantType(Participant::USER);
$participant = $this->createMock(Participant::class);
$participant->method('getAttendee')->willReturn($attendee);
$this->manager->expects(self::once())
->method('getRoomForUserByToken');
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($participant);
$this->logger->expects(self::once())
->method('debug')
->with("Participant $this->userId is not owner for calendar event integration");
$this->logger->expects(self::never())
->method('warning');
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::never())
->method('setObject');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->roomService->expects(self::never())
->method('hasExistingCalendarEvents');
$this->listener->handle($event);
}
#[DataProvider('roomUrl')]
public function testRoomNotEventRoom(string $roomUrl): void {
$calData = str_replace('{{{LOCATION}}}', $roomUrl, $this->calData);
$event = new CalendarObjectUpdatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_PHONE_LEGACY);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->logger->expects(self::once())
->method('debug');
$this->logger->expects(self::never())
->method('warning');
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::never())
->method('setObject');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->roomService->expects(self::never())
->method('hasExistingCalendarEvents');
$this->listener->handle($event);
}
public function testEventHasRRULE(): void {
$calData = <<<EOF
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T175122Z
DTSTAMP:20250310T175146Z
LAST-MODIFIED:20250310T175146Z
SEQUENCE:2
UID:2fb2416e-13f3-4945-936e-28df560b00a2
DTSTART;TZID=Europe/Vienna:20250315T100000
DTEND;TZID=Europe/Vienna:20250315T110000
STATUS:CONFIRMED
LOCATION:https://nextcloud.local/index.php/call/44wd9tvp
RRULE:FREQ=DAILY;UNTIL=20250322T090000Z
DESCRIPTION:Test
END:VEVENT
BEGIN:VTIMEZONE
TZID:Europe/Vienna
BEGIN:DAYLIGHT
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
TZNAME:CEST
DTSTART:19700329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
TZNAME:CET
DTSTART:19701025T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
END:VCALENDAR
EOF;
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_EVENT);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->roomService->expects(self::once())
->method('resetObject')
->with($room);
$this->roomService->expects(self::once())
->method('setDescription')
->with($room, 'Test');
$this->roomService->expects(self::never())
->method('setObject');
$this->logger->expects(self::once())
->method('debug');
$this->logger->expects(self::never())
->method('warning');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->roomService->expects(self::never())
->method('hasExistingCalendarEvents');
$this->listener->handle($event);
}
#[DataProvider('roomUrl')]
public function testHasExistingRooms(string $roomUrl): void {
$calData = str_replace('{{{LOCATION}}}', $roomUrl, $this->calData);
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_EVENT);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->roomService->expects(self::once())
->method('hasExistingCalendarEvents')
->willReturn(true);
$this->roomService->expects(self::once())
->method('resetObject')
->with($room);
$this->roomService->expects(self::never())
->method('setObject');
$this->logger->expects(self::once())
->method('debug');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->logger->expects(self::never())
->method('warning');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->listener->handle($event);
}
#[DataProvider('roomUrl')]
public function testDeletedEvents(string $roomUrl): void {
$calData = str_replace('{{{LOCATION}}}', $roomUrl, $this->calData);
$event = new CalendarObjectDeletedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_EVENT);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->roomService->expects(self::once())
->method('hasExistingCalendarEvents')
->willReturn(false);
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::once())
->method('setReadOnly')
->with($room, Room::READ_ONLY);
// $this->roomService->expects(self::never())
// ->method('setLobby');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->logger->expects(self::never())
->method('debug');
$this->logger->expects(self::never())
->method('warning');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->listener->handle($event);
}
#[DataProvider('roomUrl')]
public function testTime(string $roomUrl): void {
$calData = str_replace('{{{LOCATION}}}', $roomUrl, $this->calData);
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_EVENT);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->roomService->expects(self::once())
->method('hasExistingCalendarEvents')
->willReturn(false);
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::once())
->method('setObject')
->with($room, Room::OBJECT_TYPE_EVENT, '1741942800#1741946400');
// $this->roomService->expects(self::once())
// ->method('setLobby')
// ->with($room, Webinary::LOBBY_NON_MODERATORS, null);
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->logger->expects(self::never())
->method('debug');
$this->logger->expects(self::never())
->method('warning');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->listener->handle($event);
}
public function testTimezone(): void {
$calData = <<<EOF
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T180746Z
DTSTAMP:20250310T180758Z
LAST-MODIFIED:20250310T180758Z
SEQUENCE:2
UID:75847de7-3754-4aae-87a4-f03755163b66
DTSTART;VALUE=DATE:20250313
DTEND;VALUE=DATE:20250314
STATUS:CONFIRMED
LOCATION:https://nextcloud.local/index.php/call/jpmrumps
END:VEVENT
END:VCALENDAR
EOF;
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_EVENT);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->roomService->expects(self::once())
->method('hasExistingCalendarEvents')
->willReturn(false);
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::once())
->method('setObject')
->with($room, Room::OBJECT_TYPE_EVENT, '1741820400#1741906800');
// $this->roomService->expects(self::once())
// ->method('setLobby')
// ->with($room, Webinary::LOBBY_NON_MODERATORS, null);
$this->timezoneService->expects(self::once())
->method('getUserTimezone')
->willReturn('Europe/Vienna');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->logger->expects(self::never())
->method('debug');
$this->logger->expects(self::never())
->method('warning');
$this->listener->handle($event);
}
public function testTimezoneDefaultFallback(): void {
$calData = <<<EOF
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T180746Z
DTSTAMP:20250310T180758Z
LAST-MODIFIED:20250310T180758Z
SEQUENCE:2
UID:75847de7-3754-4aae-87a4-f03755163b66
DTSTART;VALUE=DATE:20250313
DTEND;VALUE=DATE:20250314
STATUS:CONFIRMED
LOCATION:https://nextcloud.local/index.php/call/jpmrumps
END:VEVENT
END:VCALENDAR
EOF;
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_EVENT);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->roomService->expects(self::once())
->method('hasExistingCalendarEvents')
->willReturn(false);
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::once())
->method('setObject')
->with($room, Room::OBJECT_TYPE_EVENT, '1741820400#1741906800');
// $this->roomService->expects(self::once())
// ->method('setLobby')
// ->with($room, Webinary::LOBBY_NON_MODERATORS, null);
$this->timezoneService->expects(self::once())
->method('getUserTimezone')
->willReturn(null);
$this->timezoneService->expects(self::once())
->method('getDefaultTimezone')
->willReturn('Europe/Vienna');
$this->logger->expects(self::never())
->method('debug');
$this->logger->expects(self::never())
->method('warning');
$this->listener->handle($event);
}
public function testTimezoneUTC(): void {
$calData = <<<EOF
BEGIN:VCALENDAR
PRODID:-//IDN nextcloud.com//Calendar app 5.2.0-dev.1//EN
CALSCALE:GREGORIAN
VERSION:2.0
BEGIN:VEVENT
CREATED:20250310T180746Z
DTSTAMP:20250310T180758Z
LAST-MODIFIED:20250310T180758Z
SEQUENCE:2
UID:75847de7-3754-4aae-87a4-f03755163b66
DTSTART;VALUE=DATE:20250313
DTEND;VALUE=DATE:20250314
STATUS:CONFIRMED
LOCATION:https://nextcloud.local/index.php/call/jpmrumps
END:VEVENT
END:VCALENDAR
EOF;
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_EVENT);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->roomService->expects(self::once())
->method('hasExistingCalendarEvents')
->willReturn(false);
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::once())
->method('setObject')
->with($room, Room::OBJECT_TYPE_EVENT, '1741824000#1741910400');
$this->timezoneService->expects(self::once())
->method('getUserTimezone')
->willReturn(null);
$this->timezoneService->expects(self::once())
->method('getDefaultTimezone')
->willReturn('Garbage');
$this->logger->expects(self::never())
->method('debug');
$this->logger->expects(self::once())
->method('warning');
$this->listener->handle($event);
}
public function testSummaryWithSurroundingWhitespaceGetsTrimmed(): void {
$calData = str_replace(
'SUMMARY:Test',
'SUMMARY: Test Meeting ',
str_replace('{{{LOCATION}}}', 'http://talk.example.com/call/12345', $this->calData)
);
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_EVENT);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->roomService->expects(self::once())
->method('hasExistingCalendarEvents')
->willReturn(false);
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::once())
->method('setName')
->with($room, 'Test Meeting');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->logger->expects(self::never())
->method('debug');
$this->logger->expects(self::never())
->method('warning');
$this->listener->handle($event);
}
public function testWhitespaceOnlySummaryBecomesEmptyString(): void {
$calData = str_replace(
'SUMMARY:Test',
'SUMMARY: ',
str_replace('{{{LOCATION}}}', 'http://talk.example.com/call/12345', $this->calData)
);
$event = new CalendarObjectCreatedEvent(1, ['principaluri' => $this->userUri], [], ['calendardata' => $calData]);
$room = $this->createMock(Room::class);
$room->method('getObjectType')->willReturn(Room::OBJECT_TYPE_EVENT);
$this->manager->expects(self::once())
->method('getRoomForUserByToken')
->willReturn($room);
$this->participantService->expects(self::once())
->method('getParticipant')
->willReturn($this->participant);
$this->roomService->expects(self::once())
->method('hasExistingCalendarEvents')
->willReturn(false);
$this->roomService->expects(self::never())
->method('resetObject');
$this->roomService->expects(self::once())
->method('setName')
->with($room, '');
$this->timezoneService->expects(self::never())
->method('getUserTimezone');
$this->timezoneService->expects(self::never())
->method('getDefaultTimezone');
$this->logger->expects(self::never())
->method('debug');
$this->logger->expects(self::never())
->method('warning');
$this->listener->handle($event);
}
}
@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Listener;
use OCA\Talk\Events\BeforeParticipantModifiedEvent;
use OCA\Talk\Exceptions\ForbiddenException;
use OCA\Talk\Listener\RestrictStartingCalls;
use OCA\Talk\Participant;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCP\IConfig;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
#[Group('DB')]
class RestrictStartingCallsTest extends TestCase {
protected IConfig&MockObject $serverConfig;
protected ParticipantService&MockObject $participantService;
protected ?RestrictStartingCalls $listener = null;
public function setUp(): void {
parent::setUp();
$this->serverConfig = $this->createMock(IConfig::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->listener = new RestrictStartingCalls($this->serverConfig, $this->participantService);
}
public static function dataCheckStartCallPermissions(): array {
return [
'default blocked' => [Room::TYPE_PUBLIC, '', false, false, true],
'allowed password request' => [Room::TYPE_PUBLIC, Room::OBJECT_TYPE_VIDEO_VERIFICATION, false, false, false],
'call active already' => [Room::TYPE_PUBLIC, '', false, true, false],
'user has permissions' => [Room::TYPE_PUBLIC, '', true, false, false],
'user has permissions & call' => [Room::TYPE_PUBLIC, '', true, true, false],
];
}
#[DataProvider('dataCheckStartCallPermissions')]
public function testCheckStartCallPermissions(int $roomType, string $roomObjectType, bool $canStart, bool $hasParticipants, bool $throws): void {
$room = $this->createMock(Room::class);
$room->method('getType')
->willReturn($roomType);
$room->method('getObjectType')
->willReturn($roomObjectType);
$participant = $this->createMock(Participant::class);
$participant->method('canStartCall')
->with($this->serverConfig)
->willReturn($canStart);
$this->participantService->method('hasActiveSessionsInCall')
->willReturn($hasParticipants);
$event = new BeforeParticipantModifiedEvent(
$room,
$participant,
'inCall',
Participant::FLAG_IN_CALL,
Participant::FLAG_DISCONNECTED
);
if ($throws) {
$this->expectException(ForbiddenException::class);
}
$this->overwriteService(RestrictStartingCalls::class, $this->listener);
$this->listener->handle($event);
$this->restoreService(RestrictStartingCalls::class);
if (!$throws) {
self::assertTrue(true);
}
}
}
+145
View File
@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php;
use OC\Authentication\Token\IProvider;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Manager;
use OCA\Talk\MatterbridgeManager;
use OCA\Talk\Service\ParticipantService;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\IAvatarManager;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Security\IRemoteHostValidator;
use OCP\Security\ISecureRandom;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
use Test\TestCase;
#[Group(name: 'DB')]
class MatterbridgeManagerTest extends TestCase {
protected IDBConnection&MockObject $db;
protected IConfig&MockObject $config;
protected IURLGenerator&MockObject $url;
protected IUserManager&MockObject $userManager;
protected Manager&MockObject $manager;
protected ParticipantService&MockObject $participantService;
protected ChatManager&MockObject $chatManager;
protected IProvider&MockObject $tokenProvider;
protected ISecureRandom&MockObject $random;
protected IAvatarManager&MockObject $avatarManager;
protected LoggerInterface&MockObject $logger;
protected ITimeFactory&MockObject $timeFactory;
protected IRemoteHostValidator&MockObject $hostValidator;
protected MatterbridgeManager $matterbridgeManager;
public function setUp(): void {
parent::setUp();
$this->db = $this->createMock(IDBConnection::class);
$this->config = $this->createMock(IConfig::class);
$this->url = $this->createMock(IURLGenerator::class);
$this->userManager = $this->createMock(IUserManager::class);
$this->manager = $this->createMock(Manager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->chatManager = $this->createMock(ChatManager::class);
$this->tokenProvider = $this->createMock(IProvider::class);
$this->random = $this->createMock(ISecureRandom::class);
$this->avatarManager = $this->createMock(IAvatarManager::class);
$this->logger = $this->createMock(LoggerInterface::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->hostValidator = $this->createMock(IRemoteHostValidator::class);
$this->matterbridgeManager = new MatterbridgeManager(
$this->db,
$this->config,
$this->url,
$this->userManager,
$this->manager,
$this->participantService,
$this->chatManager,
$this->tokenProvider,
$this->random,
$this->avatarManager,
$this->logger,
$this->timeFactory,
$this->hostValidator,
);
}
public static function dataValidateParts(): array {
return [
'Only strings allowed' => [
[['type' => false]],
[],
true,
],
'Only strings allowed unless editing' => [
[['type' => 'other', 'editing' => true]],
[],
false,
],
'Mattermost - Host only' => [
[['type' => 'mattermost', 'server' => 'yourmattermostserver.example.tld']],
[['yourmattermostserver.example.tld', true]],
false,
],
'IRC - With port' => [
[['type' => 'irc', 'server' => 'irc.example.tld:6667']],
[['irc.example.tld', true]],
false,
],
'Rocketchat - Full' => [
[['type' => 'rocketchat', 'server' => 'https://yourrocketchatserver.example.tld:443']],
[['yourrocketchatserver.example.tld', true]],
false,
],
'Rocketchat - Internal' => [
[['type' => 'rocketchat', 'server' => 'https://localhost']],
[['localhost', false]],
true,
],
'Talk' => [
[['type' => 'nctalk', 'server' => 'https://cloud.example2.tld']],
[['cloud.example2.tld', true]],
false,
],
'Talk - Internal' => [
[['type' => 'nctalk', 'server' => 'https://cloud.example.tld']],
[['cloud.example.tld', false]],
false,
],
'Talk - Internal port scan' => [
[['type' => 'nctalk', 'server' => 'https://cloud.example.tld:8080']],
[['cloud.example.tld', false]],
true,
],
];
}
#[DataProvider('dataValidateParts')]
public function testValidateParts(array $parts, array $hostValidatorData, bool $throws): void {
$this->hostValidator->method('isValid')
->willReturnMap($hostValidatorData);
$this->url->method('getAbsoluteURL')
->willReturn('https://cloud.example.tld/');
if ($throws) {
$this->expectException(\InvalidArgumentException::class);
self::invokePrivate($this->matterbridgeManager, 'validateParts', [$parts]);
} else {
$this->assertEquals($parts, self::invokePrivate($this->matterbridgeManager, 'validateParts', [$parts]));
}
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Mocks;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
class GetTurnServerListener implements IEventListener {
public function handle(Event $event): void {
$event->setServers([
[
'schemes' => 'turn',
'server' => 'turn.domain.invalid',
'username' => 'john',
'password' => 'abcde',
'protocols' => 'udp,tcp',
],
[
'schemes' => 'turns',
'server' => 'turns.domain.invalid',
'username' => 'jane',
'password' => 'ABCDE',
'protocols' => 'tcp',
],
]);
}
}
+375
View File
@@ -0,0 +1,375 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2021 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Model;
use OCA\Talk\Model\Attendee;
use OCA\Talk\Model\AttendeeMapper;
use OCA\Talk\Participant;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IDBConnection;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use Test\TestCase;
#[Group('DB')]
class AttendeeMapperTest extends TestCase {
protected ?AttendeeMapper $attendeeMapper = null;
public function setUp(): void {
parent::setUp();
$this->attendeeMapper = new AttendeeMapper(
\OCP\Server::get(IDBConnection::class)
);
}
public static function dataModifyPermissions(): array {
return [
0 => [
[
[
'actor_type' => Attendee::ACTOR_CIRCLES,
'actor_id' => 'c1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_GROUPS,
'actor_id' => 'g1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
],
Attendee::PERMISSIONS_MODIFY_SET,
Attendee::PERMISSIONS_CALL_START,
[
[
'actor_type' => Attendee::ACTOR_CIRCLES,
'actor_id' => 'c1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_GROUPS,
'actor_id' => 'g1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
],
],
1 => [
[
[
'actor_type' => Attendee::ACTOR_CIRCLES,
'actor_id' => 'c1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_GROUPS,
'actor_id' => 'g1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
],
Attendee::PERMISSIONS_MODIFY_SET,
Attendee::PERMISSIONS_CALL_START,
[
[
'actor_type' => Attendee::ACTOR_CIRCLES,
'actor_id' => 'c1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_GROUPS,
'actor_id' => 'g1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
],
],
2 => [
[
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_PUBLISH_VIDEO,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_PUBLISH_VIDEO,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_PUBLISH_VIDEO,
],
],
Attendee::PERMISSIONS_MODIFY_SET,
Attendee::PERMISSIONS_CALL_START,
[
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
],
],
3 => [
[
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_PUBLISH_VIDEO,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_VIDEO,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u2',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
],
Attendee::PERMISSIONS_MODIFY_ADD,
Attendee::PERMISSIONS_CALL_START,
[
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_PUBLISH_VIDEO + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_VIDEO + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u2',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
],
],
4 => [
[
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_PUBLISH_VIDEO + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_VIDEO + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u2',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_CALL_START,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u3',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_PUBLISH_VIDEO,
],
],
Attendee::PERMISSIONS_MODIFY_REMOVE,
Attendee::PERMISSIONS_CALL_START,
[
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'o1',
'participant_type' => Participant::OWNER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_PUBLISH_VIDEO,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'm1',
'participant_type' => Participant::MODERATOR,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_VIDEO,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u1',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u2',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM,
],
[
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'u3',
'participant_type' => Participant::USER,
'permissions' => Attendee::PERMISSIONS_CUSTOM + Attendee::PERMISSIONS_PUBLISH_AUDIO + Attendee::PERMISSIONS_PUBLISH_VIDEO,
],
],
],
];
}
#[DataProvider('dataModifyPermissions')]
public function testModifyPermissions(array $attendees, string $mode, int $permission, array $expected): void {
$roomId = 12345678;
foreach ($attendees as $attendeeData) {
try {
$attendee = $this->attendeeMapper->findByActor($roomId, $attendeeData['actor_type'], $attendeeData['actor_id']);
$this->attendeeMapper->delete($attendee);
} catch (DoesNotExistException $e) {
}
$attendee = new Attendee();
$attendee->setRoomId($roomId);
$attendee->setActorType($attendeeData['actor_type']);
$attendee->setActorId($attendeeData['actor_id']);
$attendee->setParticipantType($attendeeData['participant_type']);
$attendee->setPermissions($attendeeData['permissions']);
$this->attendeeMapper->insert($attendee);
}
$this->attendeeMapper->modifyPermissions($roomId, $mode, $permission);
foreach ($expected as $attendeeData) {
$attendee = $this->attendeeMapper->findByActor($roomId, $attendeeData['actor_type'], $attendeeData['actor_id']);
$this->assertEquals(
$attendeeData['permissions'],
$attendee->getPermissions(),
'Permissions mismatch for ' . $attendeeData['actor_type'] . '#' . $attendeeData['actor_id']
);
$this->attendeeMapper->delete($attendee);
}
}
}
File diff suppressed because it is too large Load Diff
+252
View File
@@ -0,0 +1,252 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Recording;
use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Config;
use OCA\Talk\Federation\Authenticator;
use OCA\Talk\Manager;
use OCA\Talk\Model\AttendeeMapper;
use OCA\Talk\Model\SessionMapper;
use OCA\Talk\Recording\BackendNotifier;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\RoomService;
use OCA\Talk\TalkSession;
use OCP\App\IAppManager;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Config\IUserConfig;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\Http\Client\IClientService;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Security\IHasher;
use OCP\Security\ISecureRandom;
use PHPUnit\Framework\Attributes\Group;
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 {
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' => [
],
]);
}
}
+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());
}
}
@@ -0,0 +1,174 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Settings\Admin;
use OCA\Talk\Config;
use OCA\Talk\MatterbridgeManager;
use OCA\Talk\Settings\Admin\AdminSettings;
use OCP\AppFramework\Services\IAppConfig;
use OCP\AppFramework\Services\IInitialState;
use OCP\ICacheFactory;
use OCP\IConfig;
use OCP\IGroupManager;
use OCP\IL10N;
use OCP\IUserSession;
use OCP\L10N\IFactory;
use OCP\Support\Subscription\IRegistry;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class AdminSettingsTest extends TestCase {
protected Config&MockObject $talkConfig;
protected IConfig&MockObject $serverConfig;
protected IAppConfig&MockObject $appConfig;
protected IInitialState&MockObject $initialState;
protected ICacheFactory&MockObject $cacheFactory;
protected IGroupManager&MockObject $groupManager;
protected MatterbridgeManager&MockObject $matterbridgeManager;
protected IRegistry&MockObject $subscription;
protected IUserSession&MockObject $userSession;
protected IL10N&MockObject $l10n;
protected IFactory&MockObject $l10nFactory;
protected ?AdminSettings $admin = null;
public function setUp(): void {
parent::setUp();
$this->talkConfig = $this->createMock(Config::class);
$this->serverConfig = $this->createMock(IConfig::class);
$this->appConfig = $this->createMock(IAppConfig::class);
$this->initialState = $this->createMock(IInitialState::class);
$this->cacheFactory = $this->createMock(ICacheFactory::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->matterbridgeManager = $this->createMock(MatterbridgeManager::class);
$this->subscription = $this->createMock(IRegistry::class);
$this->userSession = $this->createMock(IUserSession::class);
$this->l10n = $this->createMock(IL10N::class);
$this->l10nFactory = $this->createMock(IFactory::class);
$this->admin = $this->getAdminSettings();
}
/**
* @param string[] $methods
* @return AdminSettings|MockObject
*/
protected function getAdminSettings(array $methods = []): AdminSettings {
if (empty($methods)) {
return new AdminSettings(
$this->talkConfig,
$this->serverConfig,
$this->appConfig,
$this->initialState,
$this->cacheFactory,
$this->groupManager,
$this->matterbridgeManager,
$this->subscription,
$this->userSession,
$this->l10n,
$this->l10nFactory
);
}
return $this->getMockBuilder(AdminSettings::class)
->setConstructorArgs([
$this->talkConfig,
$this->serverConfig,
$this->appConfig,
$this->initialState,
$this->cacheFactory,
$this->groupManager,
$this->matterbridgeManager,
$this->subscription,
$this->userSession,
$this->l10n,
$this->l10nFactory,
])
->onlyMethods($methods)
->getMock();
}
public function testGetSection(): void {
$admin = $this->getAdminSettings();
$this->assertNotEmpty($admin->getSection());
}
public function testGetPriority(): void {
$admin = $this->getAdminSettings();
$this->assertEquals(0, $admin->getPriority());
}
public function testGetForm(): void {
$admin = $this->getAdminSettings([
'initGeneralSettings',
'initAllowedGroups',
'initStunServers',
'initTurnServers',
'initSignalingServers',
'initRequestSignalingServerTrial',
]);
$admin->expects($this->once())
->method('initGeneralSettings');
$admin->expects($this->once())
->method('initAllowedGroups');
$admin->expects($this->once())
->method('initStunServers');
$admin->expects($this->once())
->method('initTurnServers');
$admin->expects($this->once())
->method('initSignalingServers');
$admin->expects($this->once())
->method('initRequestSignalingServerTrial');
$form = $admin->getForm();
$this->assertSame('settings/admin-settings', $form->getTemplateName());
$this->assertSame('', $form->getRenderAs());
$this->assertCount(0, $form->getParams());
}
public function testInitStunServers(): void {
$this->talkConfig->expects($this->once())
->method('getStunServers')
->willReturn(['getStunServers']);
$this->serverConfig->expects($this->once())
->method('getSystemValueBool')
->with('has_internet_connection', true)
->willReturn(true);
$i = 0;
$expectedCalls = [
['stun_servers', ['getStunServers']],
['has_internet_connection', true],
];
$this->initialState->expects($this->exactly(2))
->method('provideInitialState')
->willReturnCallback(function () use ($expectedCalls, &$i): void {
$this->assertArrayHasKey($i, $expectedCalls);
$this->assertSame($expectedCalls[$i], func_get_args());
$i++;
});
$admin = $this->getAdminSettings();
self::invokePrivate($admin, 'initStunServers');
}
public function testInitTurnServers(): void {
$this->talkConfig->expects($this->once())
->method('getTurnServers')
->willReturn(['getTurnServers']);
$this->initialState->expects($this->once())
->method('provideInitialState')
->with('turn_servers', ['getTurnServers']);
$admin = $this->getAdminSettings();
self::invokePrivate($admin, 'initTurnServers');
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Settings\Admin;
use OCA\Talk\Settings\Admin\Section;
use OCP\IL10N;
use OCP\IURLGenerator;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class SectionTest extends TestCase {
protected IURLGenerator&MockObject $url;
protected IL10N&MockObject $l;
protected ?Section $admin = null;
public function setUp(): void {
parent::setUp();
$this->url = $this->createMock(IURLGenerator::class);
$this->l = $this->createMock(IL10N::class);
$this->admin = new Section($this->url, $this->l);
}
public function testGetID(): void {
$this->assertNotEmpty($this->admin->getID());
}
public function testGetName(): void {
$this->l->expects($this->once())
->method('t')
->with('Talk')
->willReturnArgument(0);
$this->assertNotEmpty($this->admin->getName());
}
public function testGetIcon(): void {
$this->url->expects($this->once())
->method('imagePath')
->with('spreed', 'app-dark.svg')
->willReturn('apps/spreed/img/app-dark.svg');
$this->assertNotEmpty($this->admin->getIcon());
}
public function testGetPriority(): void {
$this->assertGreaterThan(0, $this->admin->getPriority());
}
}
+443
View File
@@ -0,0 +1,443 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php\Signaling;
use OCA\Talk\Chat\ChatManager;
use OCA\Talk\Chat\MessageParser;
use OCA\Talk\Config;
use OCA\Talk\Events\ARoomModifiedEvent;
use OCA\Talk\Events\BeforeRoomDeletedEvent;
use OCA\Talk\Events\ChatMessageSentEvent;
use OCA\Talk\Events\GuestsCleanedUpEvent;
use OCA\Talk\Events\LobbyModifiedEvent;
use OCA\Talk\Events\RoomModifiedEvent;
use OCA\Talk\Events\SystemMessageSentEvent;
use OCA\Talk\Events\SystemMessagesMultipleSentEvent;
use OCA\Talk\Manager;
use OCA\Talk\Model\Message;
use OCA\Talk\Model\Thread;
use OCA\Talk\Room;
use OCA\Talk\Service\ParticipantService;
use OCA\Talk\Service\SessionService;
use OCA\Talk\Service\ThreadService;
use OCA\Talk\Signaling\BackendNotifier;
use OCA\Talk\Signaling\Listener;
use OCA\Talk\Signaling\Messages;
use OCA\Talk\Webinary;
use OCP\AppFramework\Utility\ITimeFactory;
use OCP\Comments\IComment;
use OCP\IL10N;
use OCP\L10N\IFactory;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
#[Group('DB')]
class ListenerTest extends TestCase {
protected BackendNotifier&MockObject $backendNotifier;
protected Manager&MockObject $manager;
protected ParticipantService&MockObject $participantService;
protected SessionService&MockObject $sessionService;
protected ITimeFactory&MockObject $timeFactory;
protected ?Listener $listener;
protected MessageParser&MockObject $messageParser;
protected ThreadService&MockObject $threadService;
protected Config&MockObject $config;
protected IFactory $l10nFactory;
public function setUp(): void {
parent::setUp();
$this->backendNotifier = $this->createMock(BackendNotifier::class);
$this->manager = $this->createMock(Manager::class);
$this->participantService = $this->createMock(ParticipantService::class);
$this->sessionService = $this->createMock(SessionService::class);
$this->timeFactory = $this->createMock(ITimeFactory::class);
$this->messageParser = $this->createMock(MessageParser::class);
$this->threadService = $this->createMock(ThreadService::class);
$this->l10nFactory = $this->createMock(IFactory::class);
$this->config = $this->createMock(Config::class);
$this->listener = new Listener(
$this->config,
$this->createMock(Messages::class),
$this->backendNotifier,
$this->manager,
$this->participantService,
$this->sessionService,
$this->timeFactory,
$this->messageParser,
$this->threadService,
$this->l10nFactory,
);
}
public static function dataRoomModified(): array {
return [
[
ARoomModifiedEvent::PROPERTY_NAME,
'Test room',
'name',
],
[
ARoomModifiedEvent::PROPERTY_DESCRIPTION,
'The description',
'',
],
[
ARoomModifiedEvent::PROPERTY_PASSWORD,
'password',
null,
],
[
ARoomModifiedEvent::PROPERTY_TYPE,
Room::TYPE_PUBLIC,
Room::TYPE_GROUP,
],
[
ARoomModifiedEvent::PROPERTY_READ_ONLY,
Room::READ_ONLY,
Room::READ_WRITE,
],
[
ARoomModifiedEvent::PROPERTY_LISTABLE,
Room::LISTABLE_ALL,
Room::LISTABLE_NONE,
],
];
}
#[DataProvider('dataRoomModified')]
public function testRoomModified(string $property, mixed $newValue, mixed $oldValue): void {
$room = $this->createMock(Room::class);
$event = new RoomModifiedEvent(
$room,
$property,
$newValue,
$oldValue,
null,
);
$this->backendNotifier->expects($this->once())
->method('roomModified')
->with($room);
$this->listener->handle($event);
}
public function testRecordingStatusChanged(): void {
$room = $this->createMock(Room::class);
$room->method('getCallRecording')
->willReturn(Room::RECORDING_VIDEO);
$event = new RoomModifiedEvent(
$room,
ARoomModifiedEvent::PROPERTY_CALL_RECORDING,
Room::RECORDING_VIDEO,
Room::RECORDING_NONE,
null,
);
$this->backendNotifier->expects($this->once())
->method('sendRoomMessage')
->with($room, [
'type' => 'recording',
'recording' => [
'status' => Room::RECORDING_VIDEO,
],
]);
$this->listener->handle($event);
}
public static function dataRoomLobbyModified(): array {
return [
[
Webinary::LOBBY_NON_MODERATORS,
Webinary::LOBBY_NONE,
null,
true,
],
[
Webinary::LOBBY_NONE,
Webinary::LOBBY_NON_MODERATORS,
null,
false,
],
[
Webinary::LOBBY_NONE,
Webinary::LOBBY_NON_MODERATORS,
new \DateTime(),
false,
],
];
}
#[DataProvider('dataRoomLobbyModified')]
public function testRoomLobbyModified(int $newValue, int $oldValue, ?\DateTime $lobbyTimer, bool $timerReached): void {
$room = $this->createMock(Room::class);
$event = new LobbyModifiedEvent(
$room,
$newValue,
$oldValue,
$lobbyTimer,
$timerReached,
);
$this->backendNotifier->expects($this->once())
->method('roomModified')
->with($room);
$this->listener->handle($event);
}
public function testRoomLobbyRemoved(): void {
$room = $this->createMock(Room::class);
$event = new LobbyModifiedEvent(
$room,
Webinary::LOBBY_NONE,
Webinary::LOBBY_NON_MODERATORS,
null,
true,
);
$this->backendNotifier->expects($this->once())
->method('roomModified')
->with($room);
$this->listener->handle($event);
}
public function testRoomDelete(): void {
$room = $this->createMock(Room::class);
$event = new BeforeRoomDeletedEvent(
$room
);
$this->participantService->method('getParticipantUserIds')
->with($room)
->willReturn(['user1', 'user2']);
$this->backendNotifier->expects($this->once())
->method('roomDeleted')
->with($room, ['user1', 'user2']);
$this->listener->handle($event);
}
public function testGuestsCleanedUpEvent(): void {
$room = $this->createMock(Room::class);
$event = new GuestsCleanedUpEvent(
$room
);
$this->backendNotifier->expects($this->once())
->method('participantsModified')
->with($room, []);
$this->listener->handle($event);
}
public function testChatMessageInvisibleSentEvent(): void {
$room = $this->createMock(Room::class);
$room->method('getId')->willReturn(1);
$comment = $this->createMock(IComment::class);
$comment->method('getTopmostParentId')->willReturn(null);
$comment->method('getVerb')->willReturn(ChatManager::VERB_MESSAGE);
$comment->method('getId')->willReturn(1);
$event = new ChatMessageSentEvent(
$room,
$comment,
);
$this->config->expects($this->once())
->method('isChatRelayEnabled')
->willReturn(true);
$this->messageParser->expects($this->once())
->method('createMessage');
$this->messageParser->expects($this->once())
->method('parseMessage');
$this->l10nFactory->expects($this->once())
->method('get')
->willReturn($this->createMock(IL10N::class));
$this->backendNotifier->expects($this->once())
->method('sendRoomMessage')
->with($room, [
'type' => 'chat',
'chat' => [
'refresh' => true,
],
]);
$this->listener->handle($event);
}
public function testChatMessageSentEvent(): void {
$room = $this->createMock(Room::class);
$room->method('getId')->willReturn(1);
$comment = $this->createMock(IComment::class);
$comment->method('getTopmostParentId')->willReturn(null);
$comment->method('getVerb')->willReturn(ChatManager::VERB_MESSAGE);
$comment->method('getId')->willReturn(1);
$message = $this->createConfiguredMock(Message::class, [
'getVisibility' => true,
'toArray' => [],
'getMessageId' => 123,
]);
$l10n = $this->createMock(IL10N::class);
$event = new ChatMessageSentEvent(
$room,
$comment,
);
$this->config->expects($this->once())
->method('isChatRelayEnabled')
->willReturn(true);
$this->l10nFactory->expects($this->once())
->method('get')
->willReturn($l10n);
$this->messageParser->expects($this->once())
->method('createMessage')
->with($room, null, $comment, $l10n)
->willReturn($message);
$this->messageParser->expects($this->once())
->method('parseMessage')
->with($message);
$this->backendNotifier->expects($this->once())
->method('sendRoomMessage')
->with($room, [
'type' => 'chat',
'chat' => [
'refresh' => true,
'comment' => [],
],
]);
$this->listener->handle($event);
}
public function testChatMessageSentWithThreadEvent(): void {
$room = $this->createMock(Room::class);
$room->method('getId')->willReturn(1);
$comment = $this->createMock(IComment::class);
$comment->method('getTopmostParentId')->willReturn(null);
$comment->method('getVerb')->willReturn(ChatManager::VERB_MESSAGE);
$comment->method('getId')->willReturn(1);
$message = $this->createConfiguredMock(Message::class, [
'getVisibility' => true,
'toArray' => [],
'getMessageId' => 123,
]);
$l10n = $this->createMock(IL10N::class);
$thread = $this->createMock(Thread::class);
$event = new ChatMessageSentEvent(
$room,
$comment,
);
$this->config->expects($this->once())
->method('isChatRelayEnabled')
->willReturn(true);
$this->l10nFactory->expects($this->once())
->method('get')
->willReturn($l10n);
$this->messageParser->expects($this->once())
->method('createMessage')
->with($room, null, $comment, $l10n)
->willReturn($message);
$this->messageParser->expects($this->once())
->method('parseMessage')
->with($message);
$this->threadService->expects($this->once())
->method('findByThreadId')
->willReturn($thread);
$this->backendNotifier->expects($this->once())
->method('sendRoomMessage')
->with($room, [
'type' => 'chat',
'chat' => [
'refresh' => true,
'comment' => [],
],
]);
$this->listener->handle($event);
}
public function testSystemMessageSentEvent(): void {
$room = $this->createMock(Room::class);
$comment = $this->createMock(IComment::class);
$comment->method('getVerb')->willReturn(ChatManager::VERB_SYSTEM);
$event = new SystemMessageSentEvent(
$room,
$comment,
skipLastActivityUpdate: false
);
$this->backendNotifier->expects($this->once())
->method('sendRoomMessage')
->with($room, [
'type' => 'chat',
'chat' => [
'refresh' => true,
],
]);
$this->listener->handle($event);
}
public function testSystemMessageSentEventSkippingUpdate(): void {
$room = $this->createMock(Room::class);
$comment = $this->createMock(IComment::class);
$comment->method('getMessage')->willReturn(json_encode(['message' => 'test']));
$event = new SystemMessageSentEvent(
$room,
$comment,
skipLastActivityUpdate: true
);
$this->backendNotifier->expects($this->never())
->method('sendRoomMessage');
$this->listener->handle($event);
}
public function testSystemMessagesMultipleSentEvent(): void {
$room = $this->createMock(Room::class);
$comment = $this->createMock(IComment::class);
$comment->method('getVerb')->willReturn(ChatManager::VERB_SYSTEM);
$event = new SystemMessagesMultipleSentEvent(
$room,
$comment,
);
$this->backendNotifier->expects($this->once())
->method('sendRoomMessage')
->with($room, [
'type' => 'chat',
'chat' => [
'refresh' => true,
],
]);
$this->listener->handle($event);
}
}
+120
View File
@@ -0,0 +1,120 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2018 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Talk\Tests\php;
use OCA\Talk\TalkSession;
use OCP\ISession;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use Test\TestCase;
class TalkSessionTest extends TestCase {
protected ISession&MockObject $session;
protected ?TalkSession $talkSession = null;
public function setUp(): void {
parent::setUp();
$this->session = $this->createMock(ISession::class);
$this->talkSession = new TalkSession($this->session);
}
public static function dataGet(): array {
return [
'session is null' => [null, null],
'corrupted json' => ['{invalid json', null],
'no data for token' => [json_encode(['t2' => 'd2']), null],
'valid case' => [json_encode(['t1' => 'd1']), 'd1'],
];
}
#[DataProvider('dataGet')]
public function testGetSessionForRoom(?string $sessionData, ?string $expected): void {
$this->session->expects($this->once())
->method('get')
->with('spreed-session')
->willReturn($sessionData);
$this->assertSame($expected, $this->talkSession->getSessionForRoom('t1'));
}
#[DataProvider('dataGet')]
public function testGetPasswordForRoom(?string $sessionData, ?string $expected): void {
$this->session->expects($this->once())
->method('get')
->with('spreed-password')
->willReturn($sessionData);
$this->assertSame($expected, $this->talkSession->getPasswordForRoom('t1'));
}
public static function dataSet(): array {
return [
'session is null' => [null, json_encode(['t1' => 'd1'])],
'corrupted json' => ['{invalid json', json_encode(['t1' => 'd1'])],
'no data for token' => [json_encode(['t2' => 'd2']), json_encode(['t2' => 'd2', 't1' => 'd1'])],
'update data' => [json_encode(['t1' => 'd2']), json_encode(['t1' => 'd1'])],
];
}
#[DataProvider('dataSet')]
public function testSetSessionForRoom(?string $sessionData, ?string $expected): void {
$this->session->expects($this->once())
->method('get')
->with('spreed-session')
->willReturn($sessionData);
$this->session->expects($this->once())
->method('set')
->with('spreed-session', $expected);
$this->talkSession->setSessionForRoom('t1', 'd1');
}
#[DataProvider('dataSet')]
public function testSetPasswordForRoom(?string $sessionData, ?string $expected): void {
$this->session->expects($this->once())
->method('get')
->with('spreed-password')
->willReturn($sessionData);
$this->session->expects($this->once())
->method('set')
->with('spreed-password', $expected);
$this->talkSession->setPasswordForRoom('t1', 'd1');
}
public static function dataRemove(): array {
return [
'session is null' => [null, json_encode([])],
'corrupted json' => ['{invalid json', json_encode([])],
'no data for token' => [json_encode(['t2' => 'd2']), json_encode(['t2' => 'd2'])],
'remove data' => [json_encode(['t2' => 'd2', 't1' => 'd1']), json_encode(['t2' => 'd2'])],
];
}
#[DataProvider('dataRemove')]
public function testRemoveSessionForRoom(?string $sessionData, ?string $expected): void {
$this->session->expects($this->once())
->method('get')
->with('spreed-session')
->willReturn($sessionData);
$this->session->expects($this->once())
->method('set')
->with('spreed-session', $expected);
$this->talkSession->removeSessionForRoom('t1');
}
#[DataProvider('dataRemove')]
public function testRemovePasswordForRoom(?string $sessionData, ?string $expected): void {
$this->session->expects($this->once())
->method('get')
->with('spreed-password')
->willReturn($sessionData);
$this->session->expects($this->once())
->method('set')
->with('spreed-password', $expected);
$this->talkSession->removePasswordForRoom('t1');
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
/**
* SPDX-FileCopyrightText: 2016 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
use OCP\App\IAppManager;
use OCP\Server;
if (!defined('PHPUNIT_RUN')) {
define('PHPUNIT_RUN', 1);
}
require_once __DIR__ . '/../../../../lib/base.php';
require_once __DIR__ . '/../../../../tests/autoload.php';
Server::get(IAppManager::class)->loadApp('spreed');
@@ -0,0 +1,2 @@
"name","email"
"Name","valid@example.tld"
1 name email
2 Name valid@example.tld
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,5 @@
"email","name"
"valid-1@example.tld","Valid 1"
"valid-2@example.tld","valid-2@example.tld"
"invalid","Valid 2"
"valid-1@example.tld","Valid 1 again"
1 email name
2 valid-1@example.tld Valid 1
3 valid-2@example.tld valid-2@example.tld
4 invalid Valid 2
5 valid-1@example.tld Valid 1 again
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,2 @@
"email"
"valid@example.tld"
1 email
2 valid@example.tld
@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
SPDX-License-Identifier: AGPL-3.0-or-later
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
bootstrap="bootstrap.php"
timeoutForSmallTests="900"
timeoutForMediumTests="900"
timeoutForLargeTests="900"
failOnDeprecation="true"
failOnIncomplete="true"
failOnRisky="true"
failOnWarning="true"
failOnEmptyTestSuite="true"
failOnNotice="true"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.5/phpunit.xsd"
cacheDirectory=".phpunit.result.cache">
<testsuite name="Talk App Tests">
<directory>.</directory>
</testsuite>
<logging>
</logging>
<source>
<include>
<directory>../../../spreed/appinfo</directory>
<directory>../../../spreed/lib</directory>
</include>
</source>
</phpunit>