diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..51b18f29 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + # Maintain dependencies for Composer + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7a907fb..557a7733 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['7.4', '8.0'] + php-versions: ['8.1', '8.2', '8.3'] experimental: [false] steps: @@ -30,19 +30,15 @@ jobs: run: composer install --dev --no-interaction - name: Execute tests without coverage run: tools/phpunit --testsuite="BigBlueButton unit test suite,BigBlueButton integration test suite" - + PHP: name: PHP ${{ matrix.php-versions }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: - php-versions: ['7.4','8.0'] + php-versions: ['8.1', '8.2', '8.3'] experimental: [false] - include: - - php-versions: '8.1' - experimental: true - steps: - name: Checkout uses: actions/checkout@v2 @@ -65,10 +61,17 @@ jobs: tools/phpunit --coverage-clover=build/logs/coverage.xml --testsuite="BigBlueButton unit test suite,BigBlueButton integration test suite" - name: Execute tests without coverage if: ${{ matrix.experimental }} - run: tools/phpunit --testsuite="BigBlueButton test suite" + run: tools/phpunit --testsuite="BigBlueButton unit test suite,BigBlueButton integration test suite" continue-on-error: true - - name: Coveralls - env: - COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} - if: ${{ matrix.php-versions == '7.4' && env.COVERALLS_REPO_TOKEN != null }} - run: tools/php-coveralls --coverage_clover=build/logs/coverage.xml -v + - name: Upload coverage + if: ${{ matrix.php-versions == '8.1' }} + uses: codecov/codecov-action@v3 + - name: Run rector + if: ${{ matrix.php-versions == '8.1' }} + run: tools/rector process --dry-run + - name: Run psalm + if: ${{ matrix.php-versions == '8.1' }} + run: tools/psalm + - name: Run phpstan + if: ${{ matrix.php-versions == '8.1' }} + run: tools/phpstan diff --git a/.gitignore b/.gitignore index d6bdc7e4..3fdf8edd 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ reports # # See https://github.com/littleredbutton/bigbluebutton-api-php/pull/115 for the discussion. /composer.lock + +/Makefile.iservmake diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index a1e6c9dd..93f29942 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -5,8 +5,8 @@ $finder = \PhpCsFixer\Finder::create() ->files() ->name('*.php') - ->in(__DIR__ . '/src') - ->in(__DIR__ . '/tests'); + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests'); $config = new PhpCsFixer\Config(); @@ -18,6 +18,11 @@ 'yoda_style' => false, 'single_line_throw' => false, 'increment_style' => false, + 'modernize_strpos' => false, + 'get_class_to_class_keyword' => false, + 'declare_strict_types' => [ + 'strategy' => 'enforce' + ] ]) ->setRiskyAllowed(true) ->setFinder($finder); diff --git a/README.md b/README.md index 16290b60..9b29b0ef 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # :tada: Best BigBlueButton API for PHP The unofficial and easiest to use **BigBlueButton API for PHP**, makes easy for -developers to use [BigBlueButton API] v2.2+ for **PHP 7.4+**. +developers to use [BigBlueButton API] v2.2+ for **PHP 8.1+**. ![Build Status](https://github.com/littleredbutton/bigbluebutton-api-php/workflows/CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/littleredbutton/bigbluebutton-api-php/badge.svg?branch=master)](https://coveralls.io/github/littleredbutton/bigbluebutton-api-php?branch=master) @@ -56,7 +56,7 @@ following advantages: - Development is simplified through git hooks and contributor guidelines - Documentation is up-to-date and complete - API is fixed and extended to exploit the full potential -- Require at least PHP 7.4, which allows to make the code more efficient and +- Require at least PHP 8.1, which allows to make the code more efficient and readable ## :gear: Installation and usage @@ -64,7 +64,7 @@ following advantages: In order to use this library you have to make sure to meet the following requirements: -- PHP 7.4 or above. +- PHP 8.1 or above. - curl library installed. - mbstring library installed. - xml library installed. @@ -89,7 +89,7 @@ $bbb = new BigBlueButton($apiUrl, $apiSecret); If you didn't use composer before, make sure that you include `vendor/autoload.php`. -In general the usage is closly related to the official [API description](https://docs.bigbluebutton.org/dev/api.html). This means to create a room, you have to create a `CreateMeetingParameters` object and set all required parameters via the related setter method. This means to set the `attendeePW`, you have to call `setAttendeePW` and so on. +In general the usage is closly related to the official [API description](https://docs.bigbluebutton.org/dev/api.html). This means to create a room, you have to create a `CreateMeetingParameters` object and set all required parameters via the related setter method. #### Test if API url and secret are valid ```php @@ -126,8 +126,6 @@ $version = $bbb->getApiVersion()->getVersion(); use BigBlueButton\Parameters\CreateMeetingParameters; $createMeetingParams = new CreateMeetingParameters($meetingID, $meetingName); -$createMeetingParams->setAttendeePW($attendee_password); -$createMeetingParams->setModeratorPW($moderator_password); $createMeetingResponse = $bbb->createMeeting($createMeetingParams); @@ -155,10 +153,10 @@ $createMeetingParams->setGuestPolicyAlwaysAcceptAuth(); #### Join a meeting ```php use BigBlueButton\Parameters\JoinMeetingParameters; +use BigBlueButton\Enum\Role; -$joinMeetingParams = new JoinMeetingParameters($room->uid, $displayname, $password); +$joinMeetingParams = new JoinMeetingParameters($room->uid, $displayname, Role::VIEWER); $joinMeetingParams->setCreateTime($createMeetingResponse->getCreationTime()); -$joinMeetingParams->setJoinViaHtml5(true); $joinMeetingParams->setRedirect(true); $joinUrl = $bbb->getJoinMeetingURL($joinMeetingParams); @@ -170,7 +168,7 @@ $joinUrl = $bbb->getJoinMeetingURL($joinMeetingParams); ```php use BigBlueButton\Parameters\EndMeetingParameters; -$endMeetingParams = new EndMeetingParameters($meetingID, $moderator_password); +$endMeetingParams = new EndMeetingParameters($meetingID); $response = $bbb->endMeeting($endMeetingParams); ``` @@ -206,7 +204,7 @@ if ($response->success() && $response->isRunning()) { ```php use BigBlueButton\Parameters\GetMeetingInfoParameters; -$getMeetingInfoParams = new GetMeetingInfoParameters($meetingID, $moderator_password); +$getMeetingInfoParams = new GetMeetingInfoParameters($meetingID); $response = $bbb->getMeetingInfo($getMeetingInfoParams); @@ -483,4 +481,3 @@ composer test-integration Bugs and feature request are tracked on [GitHub](https://github.com/littleredbutton/bigbluebutton-api-php/issues) [BigBlueButton API]: https://docs.bigbluebutton.org/dev/api.html - diff --git a/composer.json b/composer.json index 0ad3d191..a81e1f04 100644 --- a/composer.json +++ b/composer.json @@ -58,11 +58,12 @@ "docs": "https://github.com/littleredbutton/bigbluebutton-api-php/blob/master/README.md" }, "require": { - "php": ">=7.4", + "php": ">=8.1", "ext-curl": "*", "ext-simplexml": "*", "ext-mbstring": "*", - "ext-json": "*" + "ext-json": "*", + "ext-dom": "*" }, "suggest": { "psr/http-client-implementation": "To use the PsrHttpClientTransport.", @@ -74,11 +75,11 @@ "require-dev": { "psr/http-client": "^1.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "symfony/dotenv": "^3.4|^4.0|^5.0", - "symfony/http-client-contracts": "^1.1|^2.0", - "symfony/http-client": "^4.4|^5.0", - "symfony/process": "^3.4|^4.0|^5.0", + "psr/http-message": "^1.0 || ^2.0", + "symfony/dotenv": "^5.4|^6.4|^7.0", + "symfony/http-client-contracts": "^1.1|^2.0|^3.0", + "symfony/http-client": "^5.4|^6.4|^7.0", + "symfony/process": "^5.4|^6.4|^7.0", "nyholm/psr7": "^1.4" }, "autoload": { @@ -91,8 +92,17 @@ }, "autoload-dev": { "psr-4": { + "BigBlueButton\\Tests\\Common\\": [ + "tests/common" + ], "BigBlueButton\\Tests\\Functional\\": [ "tests/functional" + ], + "BigBlueButton\\Tests\\Integration\\": [ + "tests/integration" + ], + "BigBlueButton\\Tests\\Unit\\": [ + "tests/unit" ] } }, @@ -102,9 +112,12 @@ "test-functional": "tools/phpunit --testsuite=\"BigBlueButton functional test suite\" --exclude-group=functional-legacy", "cs-fix": "tools/php-cs-fixer fix --allow-risky=yes", "cs-test": "tools/php-cs-fixer fix --dry-run --allow-risky=yes", - "psalm": "psalm --threads=1", - "psalm-clear": "psalm --clear-cache && psalm --clear-global-cache", - "psalm-fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType", + "psalm": "tools/psalm --threads=1", + "psalm-clear": "tools/psalm --clear-cache && tools/psalm --clear-global-cache", + "psalm-fix": "tools/psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType", + "phpstan": "tools/phpstan analyse", + "rector": "tools/rector process --dry-run src/ tests/", + "rector-fix": "tools/rector process src/ tests/", "post-install-cmd": "tools/composer-git-hooks add --ignore-lock", "post-update-cmd": "tools/composer-git-hooks update" }, @@ -115,7 +128,9 @@ ], "pre-push": [ "tools/phpunit --testsuite=\"BigBlueButton unit test suite,BigBlueButton integration test suite\"", - "tools/psalm --threads=1" + "tools/psalm --threads=1", + "tools/phpstan analyse", + "tools/rector process --dry-run src/ tests/" ], "post-merge": "composer install", "post-checkout": "composer install" @@ -124,13 +139,14 @@ "brainmaestro/composer-git-hooks": "^2.8", "extensions": { "phpunit/phpunit": { - "fakerphp/faker": "^1.14" + "fakerphp/faker": "1.20.*" } }, "friendsofphp/php-cs-fixer": "^3.3", - "php-coveralls/php-coveralls": "^2.4", - "phpunit/phpunit": "^8", - "vimeo/psalm": "^4.22" + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^9", + "rector/rector": "^1.0", + "vimeo/psalm": "^5.23" } } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 00000000..9110ad71 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,17 @@ +parameters: + paths: + - src + - tests + parallel: + maximumNumberOfProcesses: 4 + level: 6 + inferPrivatePropertyTypeFromConstructor: true + bootstrapFiles: + - tools/bootstrap.php + ignoreErrors: + - + message: '#^Offset ''input'' does not exist on array\{\}\.$#' + path: tests/integration/Http/Transport/CurlTransportTest.php + - + message: '#^Offset ''vars'' does not exist on array\{\}\.$#' + path: tests/integration/Http/Transport/CurlTransportTest.php diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 00000000..ccc8bc0e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,19 @@ + + + + + ./src/ + + + + + ./tests/unit/ + + + ./tests/integration/ + + + ./tests/functional/ + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 627dd0e5..00000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - ./src/ - - - - - - ./tests/unit/ - - - ./tests/integration/ - - - ./tests/functional/ - - - - diff --git a/rector.php b/rector.php new file mode 100644 index 00000000..814ee5cf --- /dev/null +++ b/rector.php @@ -0,0 +1,34 @@ +withPaths([ + __DIR__.'/src', + __DIR__.'/tests', + ]) + ->withPhpSets() + ->withRules([ + AddVoidReturnTypeWhereNoReturnRector::class, + DeclareStrictTypesRector::class, + FinalizeTestCaseClassRector::class, + RemoveUnusedVariableAssignRector::class, + RemoveUselessParamTagRector::class, + RemoveUselessReturnTagRector::class, + TypedPropertyFromAssignsRector::class, + TypedPropertyFromStrictConstructorRector::class, + TypedPropertyFromStrictSetUpRector::class, + ]) + ->withImportNames(importShortClasses: false, removeUnusedImports: true) +; diff --git a/src/BigBlueButton.php b/src/BigBlueButton.php index 402a9a2a..8e4431d0 100644 --- a/src/BigBlueButton.php +++ b/src/BigBlueButton.php @@ -1,4 +1,7 @@ securitySecret = $secret ?: getenv('BBB_SECURITY_SALT') ?: getenv('BBB_SECRET'); - $this->bbbServerBaseUrl = $baseUrl ?: getenv('BBB_SERVER_BASE_URL'); + $securitySecret = $secret ?: getenv('BBB_SECURITY_SALT') ?: getenv('BBB_SECRET'); + + if (false === $securitySecret) { + // @codeCoverageIgnoreStart + @trigger_error(\sprintf('Constructing "%s" without passing a secret is deprecated since 6.0 and will throw an exception in 7.0.', self::class), \E_USER_DEPRECATED); + $this->securitySecret = ''; // previous behaviour + // @codeCoverageIgnoreEnd + } else { + $this->securitySecret = $securitySecret; + } + + $bbbServerBaseUrl = $baseUrl ?: getenv('BBB_SERVER_BASE_URL'); + + if (false === $bbbServerBaseUrl) { + // @codeCoverageIgnoreStart + @trigger_error(\sprintf('Constructing "%s" without passing a server base URL is deprecated since 6.0 and will throw an exception in 7.0.', self::class), \E_USER_DEPRECATED); + $this->bbbServerBaseUrl = ''; // previous behaviour + // @codeCoverageIgnoreEnd + } else { + $this->bbbServerBaseUrl = $bbbServerBaseUrl; + } if (empty($this->bbbServerBaseUrl)) { throw new ConfigException('Base url required'); } - $this->urlBuilder = new UrlBuilder($this->securitySecret, $this->bbbServerBaseUrl); + $this->urlBuilder = new UrlBuilder($this->securitySecret, $this->bbbServerBaseUrl, $this->hashingAlgorithm); $this->transport = $transport ?? CurlTransport::createWithDefaultOptions(); } @@ -162,7 +185,7 @@ public function isConnectionWorking(): bool } // HTTP exception or XML parse - } catch (\Exception $e) { + } catch (\Exception) { } $this->connectionError = self::CONNECTION_ERROR_BASEURL; @@ -199,7 +222,7 @@ public function getCreateMeetingUrl(CreateMeetingParameters $createMeetingParams */ public function createMeeting(CreateMeetingParameters $createMeetingParams): CreateMeetingResponse { - $xml = $this->processXmlResponse($this->getCreateMeetingUrl($createMeetingParams), $createMeetingParams->getPresentationsAsXML()); + $xml = $this->processXmlResponse($this->getCreateMeetingUrl($createMeetingParams), $createMeetingParams->getModules()); return new CreateMeetingResponse($xml); } @@ -411,14 +434,14 @@ public function hooksCreate(HooksCreateParameters $hookCreateParams): HooksCreat return new HooksCreateResponse($xml); } - public function getHooksListUrl(): string + public function getHooksListUrl(HooksListParameters $hooksListParameters): string { - return $this->urlBuilder->buildUrl(ApiMethod::HOOKS_LIST); + return $this->urlBuilder->buildUrl(ApiMethod::HOOKS_LIST, $hooksListParameters->getHTTPQuery()); } - public function hooksList(): HooksListResponse + public function hooksList(HooksListParameters $hooksListParameters): HooksListResponse { - $xml = $this->processXmlResponse($this->getHooksListUrl()); + $xml = $this->processXmlResponse($this->getHooksListUrl($hooksListParameters)); return new HooksListResponse($xml); } @@ -457,6 +480,23 @@ public function insertDocument(InsertDocumentParameters $insertDocumentParams): return new InsertDocumentResponse($xml); } + public function getSendChatMessageUrl(SendChatMessageParameters $sendChatMessageParams): string + { + return $this->urlBuilder->buildUrl(ApiMethod::SEND_CHAT_MESSAGE, $sendChatMessageParams->getHTTPQuery()); + } + + /** + * @throws NetworkException + * @throws ParsingException + * @throws RuntimeException + */ + public function getSendChatMessage(SendChatMessageParameters $sendChatMessageParams): SendChatMessageResponse + { + $xml = $this->processXmlResponse($this->getSendChatMessageUrl($sendChatMessageParams)); + + return new SendChatMessageResponse($xml); + } + /* ____________________ SPECIAL METHODS ___________________ */ public function getJSessionId(): ?string @@ -478,10 +518,10 @@ public function setJSessionId(string $jSessionId): void * @throws ParsingException * @throws RuntimeException */ - private function processXmlResponse(string $url, string $payload = '', string $contentType = 'application/xml'): SimpleXMLElement + private function processXmlResponse(string $url, string $payload = '', string $contentType = 'application/xml'): \SimpleXMLElement { try { - return new SimpleXMLElement($this->requestUrl($url, $payload, $contentType)); + return new \SimpleXMLElement($this->requestUrl($url, $payload, $contentType)); } catch (NetworkException|RuntimeException $e) { throw $e; } catch (\Throwable $e) { diff --git a/src/Core/ApiMethod.php b/src/Core/ApiMethod.php deleted file mode 100644 index ba4edf35..00000000 --- a/src/Core/ApiMethod.php +++ /dev/null @@ -1,42 +0,0 @@ -. - */ - -namespace BigBlueButton\Core; - -final class ApiMethod -{ - public const CREATE = 'create'; - public const JOIN = 'join'; - public const ENTER = 'enter'; - public const END = 'end'; - public const IS_MEETING_RUNNING = 'isMeetingRunning'; - public const GET_MEETING_INFO = 'getMeetingInfo'; - public const GET_MEETINGS = 'getMeetings'; - public const SIGN_OUT = 'signOut'; - public const GET_RECORDINGS = 'getRecordings'; - public const PUBLISH_RECORDINGS = 'publishRecordings'; - public const DELETE_RECORDINGS = 'deleteRecordings'; - public const UPDATE_RECORDINGS = 'updateRecordings'; - public const GET_RECORDING_TEXT_TRACKS = 'getRecordingTextTracks'; - public const PUT_RECORDING_TEXT_TRACK = 'putRecordingTextTrack'; - public const HOOKS_CREATE = 'hooks/create'; - public const HOOKS_LIST = 'hooks/list'; - public const HOOKS_DESTROY = 'hooks/destroy'; - public const INSERT_DOCUMENT = 'insertDocument'; -} diff --git a/src/Core/Attendee.php b/src/Core/Attendee.php index b71b4d9d..1ef79b21 100644 --- a/src/Core/Attendee.php +++ b/src/Core/Attendee.php @@ -1,4 +1,7 @@ */ + private array $customData = []; + + private readonly string $clientType; public function __construct(\SimpleXMLElement $xml) { @@ -124,6 +101,7 @@ public function getClientType(): string return $this->clientType; } + /** @return array */ public function getCustomData(): array { return $this->customData; diff --git a/src/Core/Hook.php b/src/Core/Hook.php index 3c173dfe..5cda828c 100644 --- a/src/Core/Hook.php +++ b/src/Core/Hook.php @@ -1,5 +1,7 @@ rawXml = $xml; - $this->hookId = (int) $xml->hookID->__toString(); - $this->callbackUrl = $xml->callbackURL->__toString(); - $this->meetingId = $xml->meetingID->__toString(); - $this->permanentHook = $xml->permanentHook->__toString() === 'true'; - $this->rawData = $xml->rawData->__toString() === 'true'; + $this->hookId = (int) $this->rawXml->hookID->__toString(); + $this->callbackUrl = $this->rawXml->callbackURL->__toString(); + $this->meetingId = $this->rawXml->meetingID->__toString(); + $this->permanentHook = $this->rawXml->permanentHook->__toString() === 'true'; + $this->rawData = $this->rawXml->rawData->__toString() === 'true'; } public function getHookId(): int diff --git a/src/Core/ImagePreview.php b/src/Core/ImagePreview.php index 737db8c7..f58d4421 100644 --- a/src/Core/ImagePreview.php +++ b/src/Core/ImagePreview.php @@ -23,24 +23,12 @@ class ImagePreview { - /** @var int */ - private $width; - - /** @var int */ - private $height; - - /** @var string */ - private $alt; - - /** @var string */ - private $url; - - public function __construct(int $width, int $height, string $alt, string $url) - { - $this->width = $width; - $this->height = $height; - $this->alt = $alt; - $this->url = $url; + public function __construct( + private readonly int $width, + private readonly int $height, + private readonly string $alt, + private readonly string $url, + ) { } public function getWidth(): int diff --git a/src/Core/Meeting.php b/src/Core/Meeting.php index 3d1dbc64..84d24ded 100644 --- a/src/Core/Meeting.php +++ b/src/Core/Meeting.php @@ -1,5 +1,7 @@ |null */ + private ?array $metas = null; - /** - * @var bool - */ - private $isBreakout; + private readonly bool $isBreakout; - public function __construct(\SimpleXMLElement $xml) + public function __construct(protected \SimpleXMLElement $rawXml) { - $this->rawXml = $xml; - $this->meetingId = $xml->meetingID->__toString(); - $this->meetingName = $xml->meetingName->__toString(); - $this->creationTime = (float) $xml->createTime; - $this->creationDate = $xml->createDate->__toString(); - $this->voiceBridge = (int) $xml->voiceBridge; - $this->dialNumber = $xml->dialNumber->__toString(); - $this->attendeePassword = $xml->attendeePW->__toString(); - $this->moderatorPassword = $xml->moderatorPW->__toString(); - $this->hasBeenForciblyEnded = $xml->hasBeenForciblyEnded->__toString() === 'true'; - $this->isRunning = $xml->running->__toString() === 'true'; - $this->participantCount = (int) $xml->participantCount; - $this->listenerCount = (int) $xml->listenerCount; - $this->voiceParticipantCount = (int) $xml->voiceParticipantCount; - $this->videoCount = (int) $xml->videoCount; - $this->duration = (int) $xml->duration; - $this->hasUserJoined = $xml->hasUserJoined->__toString() === 'true'; - $this->internalMeetingId = $xml->internalMeetingID->__toString(); - $this->isRecording = $xml->recording->__toString() === 'true'; - $this->startTime = (float) $xml->startTime; - $this->endTime = (float) $xml->endTime; - $this->maxUsers = (int) $xml->maxUsers->__toString(); - $this->moderatorCount = (int) $xml->moderatorCount->__toString(); - $this->isBreakout = $xml->isBreakout->__toString() === 'true'; + $this->meetingId = $this->rawXml->meetingID->__toString(); + $this->meetingName = $this->rawXml->meetingName->__toString(); + $this->creationTime = (float) $this->rawXml->createTime; + $this->creationDate = $this->rawXml->createDate->__toString(); + $this->voiceBridge = (int) $this->rawXml->voiceBridge; + $this->dialNumber = $this->rawXml->dialNumber->__toString(); + $this->hasBeenForciblyEnded = $this->rawXml->hasBeenForciblyEnded->__toString() === 'true'; + $this->isRunning = $this->rawXml->running->__toString() === 'true'; + $this->participantCount = (int) $this->rawXml->participantCount; + $this->listenerCount = (int) $this->rawXml->listenerCount; + $this->voiceParticipantCount = (int) $this->rawXml->voiceParticipantCount; + $this->videoCount = (int) $this->rawXml->videoCount; + $this->duration = (int) $this->rawXml->duration; + $this->hasUserJoined = $this->rawXml->hasUserJoined->__toString() === 'true'; + $this->internalMeetingId = $this->rawXml->internalMeetingID->__toString(); + $this->parentMeetingID = $this->rawXml->parentMeetingID->__toString(); + $this->isRecording = $this->rawXml->recording->__toString() === 'true'; + $this->startTime = (float) $this->rawXml->startTime; + $this->endTime = (float) $this->rawXml->endTime; + $this->maxUsers = (int) $this->rawXml->maxUsers->__toString(); + $this->moderatorCount = (int) $this->rawXml->moderatorCount->__toString(); + $this->isBreakout = $this->rawXml->isBreakout->__toString() === 'true'; } public function getMeetingId(): string @@ -213,16 +136,6 @@ public function getDialNumber(): string return $this->dialNumber; } - public function getAttendeePassword(): string - { - return $this->attendeePassword; - } - - public function getModeratorPassword(): string - { - return $this->moderatorPassword; - } - public function hasBeenForciblyEnded(): bool { return $this->hasBeenForciblyEnded; @@ -268,6 +181,11 @@ public function getInternalMeetingId(): string return $this->internalMeetingId; } + public function getParentMeetingID(): string + { + return $this->parentMeetingID; + } + public function isRecording(): bool { return $this->isRecording; @@ -317,9 +235,7 @@ public function getModerators(): array { $attendees = $this->getAttendees(); - $moderators = array_filter($attendees, function ($attendee) { - return $attendee->getRole() === 'MODERATOR'; - }); + $moderators = array_filter($attendees, static fn ($attendee) => $attendee->getRole() === Role::MODERATOR->value); return array_values($moderators); } @@ -333,13 +249,12 @@ public function getViewers(): array { $attendees = $this->getAttendees(); - $viewers = array_filter($attendees, function ($attendee) { - return $attendee->getRole() === 'VIEWER'; - }); + $viewers = array_filter($attendees, static fn ($attendee) => $attendee->getRole() === Role::VIEWER->value); return array_values($viewers); } + /** @return array */ public function getMetas(): array { if ($this->metas === null) { diff --git a/src/Core/PlaybackFormat.php b/src/Core/PlaybackFormat.php index bf9cb728..ef1209e1 100644 --- a/src/Core/PlaybackFormat.php +++ b/src/Core/PlaybackFormat.php @@ -23,23 +23,18 @@ class PlaybackFormat { - /** @var string */ - private $type; + private readonly string $type; - /** @var string */ - private $url; + private readonly string $url; - /** @var int */ - private $processingTime; + private readonly int $processingTime; - /** @var int */ - private $length; + private readonly int $length; /** @var ImagePreview[] */ - private $imagePreviews; + private ?array $imagePreviews = null; - /** @var \SimpleXMLElement */ - private $imagePreviewsRaw; + private readonly ?\SimpleXMLElement $imagePreviewsRaw; public function __construct(\SimpleXMLElement $xml) { diff --git a/src/Core/Record.php b/src/Core/Record.php index dfa15b92..c011bf57 100644 --- a/src/Core/Record.php +++ b/src/Core/Record.php @@ -1,4 +1,7 @@ */ + private array $metas = []; /** @var PlaybackFormat[] */ - private $playbackFormats = []; + private array $playbackFormats = []; public function __construct(\SimpleXMLElement $xml) { $this->recordId = $xml->recordID->__toString(); $this->meetingId = $xml->meetingID->__toString(); + $this->internalMeetingId = $xml->internalMeetingID->__toString(); $this->name = $xml->name->__toString(); $this->isPublished = $xml->published->__toString() === 'true'; $this->state = $xml->state->__toString(); $this->startTime = (float) $xml->startTime->__toString(); $this->endTime = (float) $xml->endTime->__toString(); $this->participantCount = (int) $xml->participants->__toString(); - $this->playbackType = $xml->playback->format->type->__toString(); - $this->playbackUrl = $xml->playback->format->url->__toString(); - $this->playbackLength = (int) $xml->playback->format->length->__toString(); foreach ($xml->playback->children() as $format) { $this->playbackFormats[] = new PlaybackFormat($format); @@ -70,6 +73,11 @@ public function getMeetingId(): string return $this->meetingId; } + public function getInternalMeetingId(): string + { + return $this->internalMeetingId; + } + public function getName(): string { return $this->name; @@ -100,6 +108,7 @@ public function getParticipantCount(): int return $this->participantCount; } + /** @return array */ public function getMetas(): array { return $this->metas; diff --git a/src/Core/Track.php b/src/Core/Track.php index e1807f56..7ef014ac 100644 --- a/src/Core/Track.php +++ b/src/Core/Track.php @@ -1,4 +1,7 @@ . + */ + +namespace BigBlueButton\Enum; + +/** + * @psalm-immutable + */ +enum ApiMethod: string +{ + case CREATE = 'create'; + case JOIN = 'join'; + case ENTER = 'enter'; + case END = 'end'; + case IS_MEETING_RUNNING = 'isMeetingRunning'; + case GET_MEETING_INFO = 'getMeetingInfo'; + case GET_MEETINGS = 'getMeetings'; + case SIGN_OUT = 'signOut'; + case GET_RECORDINGS = 'getRecordings'; + case PUBLISH_RECORDINGS = 'publishRecordings'; + case DELETE_RECORDINGS = 'deleteRecordings'; + case UPDATE_RECORDINGS = 'updateRecordings'; + case GET_RECORDING_TEXT_TRACKS = 'getRecordingTextTracks'; + case PUT_RECORDING_TEXT_TRACK = 'putRecordingTextTrack'; + case HOOKS_CREATE = 'hooks/create'; + case HOOKS_LIST = 'hooks/list'; + case HOOKS_DESTROY = 'hooks/destroy'; + case INSERT_DOCUMENT = 'insertDocument'; + case SEND_CHAT_MESSAGE = 'sendChatMessage'; +} diff --git a/src/Enum/Feature.php b/src/Enum/Feature.php new file mode 100644 index 00000000..05be5571 --- /dev/null +++ b/src/Enum/Feature.php @@ -0,0 +1,65 @@ +. + */ + +namespace BigBlueButton\Enum; + +/** + * @psalm-immutable + */ +enum Feature: string +{ + case BREAKOUT_ROOMS = 'breakoutRooms'; + case CAPTIONS = 'captions'; + case CHAT = 'chat'; + case PRIVATE_CHAT = 'privateChat'; + case DOWNLOAD_PRESENTATION_WITH_ANNOTATIONS = 'downloadPresentationWithAnnotations'; + case EXTERNAL_VIDEOS = 'externalVideos'; + case IMPORT_PRESENTATION_WITH_ANNOTATIONS_FROM_BREAKOUT_ROOMS = 'importPresentationWithAnnotationsFromBreakoutRooms'; + case IMPORT_SHARED_NOTES_FROM_BREAKOUT_ROOMS = 'importSharedNotesFromBreakoutRooms'; + case LAYOUTS = 'layouts'; + case LEARNING_DASHBOARD = 'learningDashboard'; + case LEARNING_DASHBOARD_DOWNLOAD_SESSION_DATA = 'learningDashboardDownloadSessionData'; + case POLLS = 'polls'; + case SCREENSHARE = 'screenshare'; + case SHARED_NOTES = 'sharedNotes'; + case VIRTUAL_BACKGROUNDS = 'virtualBackgrounds'; + case CUSTOM_VIRTUAL_BACKGROUNDS = 'customVirtualBackgrounds'; + case LIVE_TRANSCRIPTION = 'liveTranscription'; + case PRESENTATION = 'presentation'; + case CAMERA_AS_CONTENT = 'cameraAsContent'; + case SNAPSHOT_OF_CURRENT_SLIDE = 'snapshotOfCurrentSlide'; + case DOWNLOAD_PRESENTATION_ORIGINAL_FILE = 'downloadPresentationOriginalFile'; + case DOWNLOAD_PRESENTATION_CONVERTED_TO_PDF = 'downloadPresentationConvertedToPdf'; + case TIMER = 'timer'; + case INFINITE_WHITEBOARD = 'infiniteWhiteboard'; + case DELETE_CHAT_MESSAGE = 'deleteChatMessage'; + case EDIT_CHAT_MESSAGE = 'editChatMessage'; + case REPLY_CHAT_MESSAGE = 'replyChatMessage'; + case CHAT_MESSAGE_REACTIONS = 'chatMessageReactions'; + case RAISE_HAND = 'raiseHand'; + /** @deprecated BC only. Use Feature::USER_REACTIONS instead. */ + case USER_ACTIONS = 'userActions'; + case USER_REACTIONS = 'userReactions'; + case CHAT_EMOJI_PICKER = 'chatEmojiPicker'; + case QUIZZES = 'quizzes'; + case PLUGINS = 'plugins'; +} diff --git a/src/Core/GuestPolicy.php b/src/Enum/GuestPolicy.php similarity index 77% rename from src/Core/GuestPolicy.php rename to src/Enum/GuestPolicy.php index a9177160..7dbba881 100644 --- a/src/Core/GuestPolicy.php +++ b/src/Enum/GuestPolicy.php @@ -20,12 +20,15 @@ * with BigBlueButton; if not, see . */ -namespace BigBlueButton\Core; +namespace BigBlueButton\Enum; -final class GuestPolicy +/** + * @psalm-immutable + */ +enum GuestPolicy: string { - public const ALWAYS_ACCEPT = 'ALWAYS_ACCEPT'; - public const ALWAYS_DENY = 'ALWAYS_DENY'; - public const ASK_MODERATOR = 'ASK_MODERATOR'; - public const ALWAYS_ACCEPT_AUTH = 'ALWAYS_ACCEPT_AUTH'; + case ALWAYS_ACCEPT = 'ALWAYS_ACCEPT'; + case ALWAYS_DENY = 'ALWAYS_DENY'; + case ASK_MODERATOR = 'ASK_MODERATOR'; + case ALWAYS_ACCEPT_AUTH = 'ALWAYS_ACCEPT_AUTH'; } diff --git a/src/Core/MeetingLayout.php b/src/Enum/HashingAlgorithm.php similarity index 71% rename from src/Core/MeetingLayout.php rename to src/Enum/HashingAlgorithm.php index 0f499913..a6bd8100 100644 --- a/src/Core/MeetingLayout.php +++ b/src/Enum/HashingAlgorithm.php @@ -2,10 +2,10 @@ declare(strict_types=1); -/** +/* * BigBlueButton open source conferencing system - https://www.bigbluebutton.org/. * - * Copyright (c) 2016-2021 BigBlueButton Inc. and by respective authors (see below). + * Copyright (c) 2016-2023 BigBlueButton Inc. and by respective authors (see below). * * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software @@ -20,12 +20,15 @@ * with BigBlueButton; if not, see . */ -namespace BigBlueButton\Core; +namespace BigBlueButton\Enum; -final class MeetingLayout +/** + * @psalm-immutable + */ +enum HashingAlgorithm: string { - public const CUSTOM_LAYOUT = 'CUSTOM_LAYOUT'; - public const SMART_LAYOUT = 'SMART_LAYOUT'; - public const PRESENTATION_FOCUS = 'PRESENTATION_FOCUS'; - public const VIDEO_FOCUS = 'VIDEO_FOCUS'; + case SHA_1 = 'sha1'; + case SHA_256 = 'sha256'; + case SHA_512 = 'sha512'; + case SHA_384 = 'sha384'; } diff --git a/src/Enum/MeetingLayout.php b/src/Enum/MeetingLayout.php new file mode 100644 index 00000000..b359459d --- /dev/null +++ b/src/Enum/MeetingLayout.php @@ -0,0 +1,39 @@ +. + */ + +namespace BigBlueButton\Enum; + +/** + * @psalm-immutable + */ +enum MeetingLayout: string +{ + case UNIFIED_LAYOUT = 'UNIFIED_LAYOUT'; + case CUSTOM_LAYOUT = 'CUSTOM_LAYOUT'; + case SMART_LAYOUT = 'SMART_LAYOUT'; + case PRESENTATION_FOCUS = 'PRESENTATION_FOCUS'; + case VIDEO_FOCUS = 'VIDEO_FOCUS'; + case CAMERAS_ONLY = 'CAMERAS_ONLY'; + case PARTICIPANTS_CHAT_ONLY = 'PARTICIPANTS_CHAT_ONLY'; + case PRESENTATION_ONLY = 'PRESENTATION_ONLY'; + case MEDIA_ONLY = 'MEDIA_ONLY'; +} diff --git a/src/Enum/Role.php b/src/Enum/Role.php new file mode 100644 index 00000000..4ac061ff --- /dev/null +++ b/src/Enum/Role.php @@ -0,0 +1,32 @@ +. + */ + +namespace BigBlueButton\Enum; + +/** + * @psalm-immutable + */ +enum Role: string +{ + case MODERATOR = 'MODERATOR'; + case VIEWER = 'VIEWER'; +} diff --git a/src/Exceptions/BaseException.php b/src/Exceptions/BaseException.php index bd6e8576..68f42be0 100644 --- a/src/Exceptions/BaseException.php +++ b/src/Exceptions/BaseException.php @@ -21,11 +21,9 @@ namespace BigBlueButton\Exceptions; -use Exception; - /** * @abstract since 4.0. */ -class BaseException extends Exception +class BaseException extends \Exception { } diff --git a/src/Http/SetCookie.php b/src/Http/SetCookie.php index b7854496..923ad56f 100644 --- a/src/Http/SetCookie.php +++ b/src/Http/SetCookie.php @@ -32,12 +32,10 @@ * * @internal */ -final class SetCookie +final class SetCookie implements \Stringable { - /** - * @var array - */ - private static $defaults = [ + /** @var array */ + private static array $defaults = [ 'Name' => null, 'Value' => null, 'Domain' => null, @@ -50,9 +48,9 @@ final class SetCookie ]; /** - * @var array Cookie data + * @var array Cookie data */ - private $data; + private ?array $data; /** * Create a new SetCookie object from a string. @@ -66,7 +64,7 @@ public static function fromString(string $cookie): self // Explode the cookie string using a series of semicolons $pieces = array_filter(array_map('trim', explode(';', $cookie))); // The name of the cookie (first kvp) must exist and include an equal sign. - if (!isset($pieces[0]) || strpos($pieces[0], '=') === false) { + if (!isset($pieces[0]) || !str_contains($pieces[0], '=')) { return new self($data); } @@ -98,19 +96,11 @@ public static function fromString(string $cookie): self } /** - * @param array $data Array of cookie data provided by a Cookie parser + * @param array $data Array of cookie data provided by a Cookie parser */ public function __construct(array $data = []) { - /** @var array|null $replaced will be null in case of replace error */ - $replaced = array_replace(self::$defaults, $data); - // @codeCoverageIgnoreStart - if ($replaced === null) { - throw new \InvalidArgumentException('Unable to replace the default values for the Cookie.'); - } - // @codeCoverageIgnoreEnd - - $this->data = $replaced; + $this->data = array_replace(self::$defaults, $data); // Extract the Expires value and turn it into a UNIX timestamp if needed if (!$this->getExpires() && $this->getMaxAge()) { // Calculate the Expires date @@ -120,13 +110,13 @@ public function __construct(array $data = []) } } - public function __toString() + public function __toString(): string { $str = $this->data['Name'].'='.$this->data['Value'].'; '; foreach ($this->data as $k => $v) { if ($k !== 'Name' && $k !== 'Value' && $v !== null && $v !== false) { if ($k === 'Expires') { - $str .= 'Expires='.gmdate('D, d M Y H:i:s \G\M\T', $v).'; '; + $str .= 'Expires='.gmdate('D, d M Y H:i:s \G\M\T', (int) $v).'; '; } else { $str .= ($v === true ? $k : "{$k}={$v}").'; '; } @@ -136,6 +126,7 @@ public function __toString() return rtrim($str, '; '); } + /** @return array */ public function toArray(): array { return $this->data; @@ -216,7 +207,7 @@ public function setPath(string $path): void */ public function getMaxAge(): ?int { - return $this->data['Max-Age'] == null ? null : (int) $this->data['Max-Age']; + return $this->data['Max-Age'] === null ? null : (int) $this->data['Max-Age']; } /** @@ -231,10 +222,8 @@ public function setMaxAge(int $maxAge): void /** * The UNIX timestamp when the cookie Expires. - * - * @return string|int|null */ - public function getExpires() + public function getExpires(): int|string|null { return $this->data['Expires']; } @@ -244,7 +233,7 @@ public function getExpires() * * @param int|string $timestamp unix timestamp or any English textual datetime description */ - public function setExpires($timestamp): void + public function setExpires(int|string $timestamp): void { $this->data['Expires'] = is_numeric($timestamp) ? (int) $timestamp @@ -325,22 +314,22 @@ public function matchesPath(string $requestPath): bool $cookiePath = $this->getPath(); // Match on exact matches or when path is the default empty "/" - if ($cookiePath === '/' || $cookiePath == $requestPath) { + if ($cookiePath === '/' || $cookiePath === $requestPath) { return true; } // Ensure that the cookie-path is a prefix of the request path. - if (0 !== strpos($requestPath, $cookiePath)) { + if (!str_starts_with($requestPath, $cookiePath)) { return false; } // Match if the last character of the cookie-path is "/" - if (substr($cookiePath, -1, 1) === '/') { + if (str_ends_with($cookiePath, '/')) { return true; } // Match if the first character not included in cookie path is "/" - return substr($requestPath, \strlen($cookiePath), 1) === '/'; + return $requestPath[\strlen($cookiePath)] === '/'; } /** @@ -386,7 +375,7 @@ public function isExpired(): bool * * @return bool|string Returns true if valid or an error message if invalid */ - public function validate() + public function validate(): bool|string { $name = $this->getName(); if ($name === '') { diff --git a/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php b/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php index a8b10278..befa70d9 100644 --- a/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php +++ b/src/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransport.php @@ -34,7 +34,7 @@ // @codeCoverageIgnoreStart if (!interface_exists(ClientInterface::class)) { - throw new \LogicException(sprintf( + throw new \LogicException(\sprintf( 'The "%s" interface was not found. '. 'You cannot use "%s" without it.'. 'Try running "composer require" for a package which provides psr/http-client-implementation.', @@ -44,7 +44,7 @@ } if (!interface_exists(RequestFactoryInterface::class)) { - throw new \LogicException(sprintf( + throw new \LogicException(\sprintf( 'The "%s" interface was not found. '. 'You cannot use "%s" without it.'. 'Try running "composer require" for a package which provides psr/http-factory-implementation.', @@ -54,7 +54,7 @@ } if (!interface_exists(StreamFactoryInterface::class)) { - throw new \LogicException(sprintf( + throw new \LogicException(\sprintf( 'The "%s" interface was not found. '. 'You cannot use "%s" without it.'. 'Try running "composer require" for a package which provides psr/http-factory-implementation.', @@ -69,44 +69,13 @@ */ final class PsrHttpClientTransport implements TransportInterface { - /** - * @var ClientInterface - */ - private $httpClient; - - /** - * @var RequestFactoryInterface - */ - private $requestFactory; - - /** - * @var StreamFactoryInterface - */ - private $streamFactory; - - /** - * @var string[] - */ - private $defaultHeaders; - /** * @param string[] $defaultHeaders additional headers to pass on each request */ - public function __construct( - ClientInterface $httpClient, - RequestFactoryInterface $requestFactory, - StreamFactoryInterface $streamFactory, - array $defaultHeaders = [] - ) { - $this->httpClient = $httpClient; - $this->requestFactory = $requestFactory; - $this->streamFactory = $streamFactory; - $this->defaultHeaders = $defaultHeaders; + public function __construct(private readonly ClientInterface $httpClient, private readonly RequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly array $defaultHeaders = []) + { } - /** - * {@inheritDoc} - */ public function request(TransportRequest $request): TransportResponse { if ('' !== $payload = $request->getPayload()) { @@ -124,7 +93,7 @@ public function request(TransportRequest $request): TransportResponse try { $psrResponse = $this->httpClient->sendRequest($psrRequest); } catch (ClientExceptionInterface $e) { - throw new RuntimeException(sprintf('HTTP request failed: %s', $e->getMessage()), 0, $e); + throw new RuntimeException(\sprintf('HTTP request failed: %s', $e->getMessage()), 0, $e); } if ($psrResponse->getStatusCode() < 200 || $psrResponse->getStatusCode() >= 300) { diff --git a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php index 943ac80e..0545c74d 100644 --- a/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php +++ b/src/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransport.php @@ -38,7 +38,7 @@ // @codeCoverageIgnoreStart if (!interface_exists(HttpClientInterface::class)) { - throw new \LogicException(sprintf( + throw new \LogicException(\sprintf( 'The "%s" interface was not found. '. 'You cannot use "%s" without it.'. 'Try running "composer require" for a package which provides symfony/http-client-implementation.', @@ -53,30 +53,12 @@ */ final class SymfonyHttpClientTransport implements TransportInterface { - /** - * @var HttpClientInterface - */ - private $httpClient; - - /** - * @var string[] - */ - private $defaultHeaders; - - /** - * @var mixed[] - */ - private $defaultOptions; - /** * @param string[] $defaultHeaders additional HTTP headers to pass on each request * @param mixed[] $defaultOptions Options for Symfony HTTP client passed on every request. See {@link https://symfony.com/doc/current/http_client.html} for details. */ - public function __construct(HttpClientInterface $httpClient, array $defaultHeaders = [], array $defaultOptions = []) + public function __construct(private readonly HttpClientInterface $httpClient, private readonly array $defaultHeaders = [], private readonly array $defaultOptions = []) { - $this->httpClient = $httpClient; - $this->defaultHeaders = $defaultHeaders; - $this->defaultOptions = $defaultOptions; } /** @@ -89,7 +71,7 @@ public static function create(array $defaultHeaders = [], array $defaultOptions { // @codeCoverageIgnoreStart if (!class_exists(HttpClient::class)) { - throw new \LogicException(sprintf( + throw new \LogicException(\sprintf( 'Cannot create an instance of "%s" when Symfony HttpClient is not installed. '. 'Either instantiate the class by yourself and pass a proper implementation or '. 'try to run "composer require symfony/http-client".', @@ -101,9 +83,6 @@ public static function create(array $defaultHeaders = [], array $defaultOptions // @codeCoverageIgnoreEnd } - /** - * {@inheritDoc} - */ public function request(TransportRequest $request): TransportResponse { $headers = $this->defaultHeaders; @@ -134,7 +113,7 @@ public function request(TransportRequest $request): TransportResponse return new TransportResponse($symfonyResponse->getContent(), self::extractJsessionCookie($symfonyResponse)); } catch (TransportExceptionInterface $e) { - throw new RuntimeException(sprintf('HTTP request failed: %s', $e->getMessage()), 0, $e); + throw new RuntimeException(\sprintf('HTTP request failed: %s', $e->getMessage()), 0, $e); } catch (ClientExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface $e) { throw new NetworkException('Bad response.', $e->getCode(), $e); } diff --git a/src/Http/Transport/CurlTransport.php b/src/Http/Transport/CurlTransport.php index 40d0b7eb..8f537677 100644 --- a/src/Http/Transport/CurlTransport.php +++ b/src/Http/Transport/CurlTransport.php @@ -55,11 +55,6 @@ final class CurlTransport implements TransportInterface */ private const DEFAULT_TIMEOUT = 30; - /** - * @var mixed[] - */ - private $curlOptions; - /** * Allows to inject custom cURL options used on dispatching request to BBB. * Please note that you must ensure on your own that the usage of custom options does not break the transport. @@ -69,9 +64,8 @@ final class CurlTransport implements TransportInterface * * @param mixed[] $curlOptions A list of cURL options to pass to the cURL handle. Option name as key, option value as value. */ - public function __construct(array $curlOptions = []) + public function __construct(private readonly array $curlOptions = []) { - $this->curlOptions = $curlOptions; } /** @@ -84,9 +78,6 @@ public static function createWithDefaultOptions(array $additionalCurlOptions = [ // @codeCoverageIgnoreEnd } - /** - * {@inheritDoc} - */ public function request(TransportRequest $request): TransportResponse { // @codeCoverageIgnoreStart @@ -98,7 +89,7 @@ public function request(TransportRequest $request): TransportResponse $ch = curl_init(); // @codeCoverageIgnoreStart if (!$ch) { - throw new RuntimeException('Could not create curl instance. Error: '.curl_error($ch)); + throw new RuntimeException('Could not create curl instance.'); } // @codeCoverageIgnoreEnd @@ -130,6 +121,7 @@ public function request(TransportRequest $request): TransportResponse return new TransportResponse($data, $sessionId); } + /** @return array */ private static function buildPostOptions(TransportRequest $request): array { $options = []; @@ -140,13 +132,14 @@ private static function buildPostOptions(TransportRequest $request): array $options[\CURLOPT_POSTFIELDS] = $payload; $options[\CURLOPT_HTTPHEADER] = [ 'Content-type: '.$request->getContentType(), - 'Content-length: '.mb_strlen($payload), + 'Content-length: '.mb_strlen((string) $payload), ]; } return $options; } + /** @return array */ private static function buildUrlOptions(TransportRequest $request): array { return [ @@ -160,6 +153,8 @@ private static function buildUrlOptions(TransportRequest $request): array * The CURLOPT_HTTPHEADER will be treated in a special * way and merged instead, but on values with same header name * only the header from the first option set will be preserved. + * + * @return array */ private static function mergeCurlOptions(array ...$options): array { @@ -186,26 +181,16 @@ private static function mergeCurlOptions(array ...$options): array /** * A raw response as returned from cURL will contain the headers followed by "\r\n\r\n" and the content. * - * @param \CurlHandle|resource $curlHandle + * @return (string|string[][])[] First key headers, second key is content + * + * @see https://stackoverflow.com/questions/10589889/returning-header-as-array-using-curl * - * @return array{0: string, 1: string[]} First key headers, second key is content + * @psalm-return array{0: array>, 1: string} * * @throws NetworkException - * - * @see https://stackoverflow.com/questions/10589889/returning-header-as-array-using-curl */ - private static function getHeadersAndContentFromCurlHandle($curlHandle): array + private static function getHeadersAndContentFromCurlHandle(\CurlHandle $curlHandle): array { - /* @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ - // @codeCoverageIgnoreStart - if (\PHP_VERSION_ID >= 80000 && !$curlHandle instanceof \CurlHandle) { - /* @noinspection PhpElementIsNotAvailableInCurrentPhpVersionInspection */ - throw new \InvalidArgumentException(sprintf('$curlHandle must be "%s". "%s" given.', \CurlHandle::class, get_debug_type($curlHandle))); - } elseif (\PHP_VERSION_ID < 80000 && !\is_resource($curlHandle)) { - throw new \InvalidArgumentException(sprintf('$curlHandle must be resource. "%s" given.', \is_object($curlHandle) ? \get_class($curlHandle) : \gettype($curlHandle))); - } - // @codeCoverageIgnoreEnd - $headers = []; curl_setopt($curlHandle, \CURLOPT_HEADER, 1); @@ -234,7 +219,7 @@ private static function getHeadersAndContentFromCurlHandle($curlHandle): array $splitHeader = explode(': ', $line, 2); // @codeCoverageIgnoreStart if (!isset($splitHeader[0], $splitHeader[1])) { - throw new \InvalidArgumentException(sprintf('Header value "%s" is invalid. Expected format is "Header-Name: value".', $line)); + throw new \InvalidArgumentException(\sprintf('Header value "%s" is invalid. Expected format is "Header-Name: value".', $line)); } // @codeCoverageIgnoreEnd diff --git a/src/Http/Transport/Header.php b/src/Http/Transport/Header.php index 1a38bf2a..ea339ee2 100644 --- a/src/Http/Transport/Header.php +++ b/src/Http/Transport/Header.php @@ -43,15 +43,15 @@ public static function mergeCurlHeaders(array ...$headers): array foreach ($headers as $headerSet) { foreach ($headerSet as $header) { if (!\is_string($header)) { - throw new \InvalidArgumentException(sprintf( + throw new \InvalidArgumentException(\sprintf( 'Non-string header with type "%s" passed.', - \is_object($header) ? \get_class($header) : \gettype($header) + get_debug_type($header) )); } $splitHeader = explode(': ', $header, 2); if (!isset($splitHeader[0], $splitHeader[1])) { - throw new \InvalidArgumentException(sprintf('Header value "%s" is invalid. Expected format is "Header-Name: value".', $header)); + throw new \InvalidArgumentException(\sprintf('Header value "%s" is invalid. Expected format is "Header-Name: value".', $header)); } // Enforce lower case for header names to avoid duplicates in mixed case. The case of header names should @@ -62,7 +62,7 @@ public static function mergeCurlHeaders(array ...$headers): array $result = []; foreach ($mergedHeaders as $header => $value) { - $result[] = sprintf('%s: %s', $header, $value); + $result[] = \sprintf('%s: %s', $header, $value); } return $result; diff --git a/src/Http/Transport/TransportRequest.php b/src/Http/Transport/TransportRequest.php index 0217f1ef..b1ae3ced 100644 --- a/src/Http/Transport/TransportRequest.php +++ b/src/Http/Transport/TransportRequest.php @@ -26,26 +26,8 @@ */ class TransportRequest { - /** - * @var string - */ - private $url; - - /** - * @var string - */ - private $payload; - - /** - * @var string - */ - private $contentType; - - public function __construct(string $url, string $payload, string $contentType) + public function __construct(private readonly string $url, private readonly string $payload, private readonly string $contentType) { - $this->url = $url; - $this->payload = $payload; - $this->contentType = $contentType; } public function getUrl(): string diff --git a/src/Http/Transport/TransportResponse.php b/src/Http/Transport/TransportResponse.php index 650961c0..42a97dd3 100644 --- a/src/Http/Transport/TransportResponse.php +++ b/src/Http/Transport/TransportResponse.php @@ -26,20 +26,8 @@ */ class TransportResponse { - /** - * @var string - */ - private $body; - - /** - * @var string|null - */ - private $sessionId; - - public function __construct(string $body, ?string $sessionId) + public function __construct(private readonly string $body, private readonly ?string $sessionId) { - $this->body = $body; - $this->sessionId = $sessionId; } public function getBody(): string diff --git a/src/Parameters/BaseParameters.php b/src/Parameters/BaseParameters.php index f87e95c5..6bf144f2 100644 --- a/src/Parameters/BaseParameters.php +++ b/src/Parameters/BaseParameters.php @@ -1,4 +1,7 @@ */ + protected array $ignoreProperties = []; /** + * @param array $arguments + * * @return $this|bool|mixed|null */ public function __call(string $name, array $arguments) @@ -34,27 +40,28 @@ public function __call(string $name, array $arguments) if (!preg_match('/^(get|is|set)[A-Z]/', $name)) { throw new \BadFunctionCallException($name.' does not exist'); } - if (strpos($name, 'get') === 0) { + if (str_starts_with($name, 'get')) { return $this->getter(lcfirst(substr($name, 3))); - } elseif (strpos($name, 'is') === 0) { + } + + if (str_starts_with($name, 'is')) { return $this->booleanGetter(lcfirst(substr($name, 2))); - } elseif (strpos($name, 'set') === 0) { + } + + if (str_starts_with($name, 'set')) { return $this->setter(lcfirst(substr($name, 3)), $arguments); } return null; } - /** - * @return mixed - */ - protected function getter(string $name) + protected function getter(string $name): mixed { if (property_exists($this, $name)) { return $this->$name; - } else { - throw new \BadFunctionCallException($name.' is not a valid property'); } + + throw new \BadFunctionCallException($name.' is not a valid property'); } protected function booleanGetter(string $name): ?bool @@ -68,36 +75,51 @@ protected function booleanGetter(string $name): ?bool return $value; } - protected function setter(string $name, array $arguments): self + /** @param array $arguments */ + protected function setter(string $name, array $arguments): static { if (!property_exists($this, $name)) { throw new \BadFunctionCallException($name.' is not a valid property'); } + $property = new \ReflectionProperty($this, $name); + $type = $property->getType(); + + // Construct enum on demand + if ($type instanceof \ReflectionNamedType && enum_exists($type->getName()) && !\is_object($arguments[0])) { + /* @phpstan-ignore-next-line */ + $arguments[0] = ($type->getName())::from($arguments[0]); + } $this->$name = $arguments[0]; return $this; } + /** @return array */ protected function getProperties(): array { - return array_filter(get_object_vars($this), function ($name) { - return $name !== 'ignoreProperties' && !\in_array($name, $this->ignoreProperties); - }, \ARRAY_FILTER_USE_KEY); + return array_filter(get_object_vars($this), fn ($name) => $name !== 'ignoreProperties' && !\in_array( + $name, + $this->ignoreProperties, + true + ), \ARRAY_FILTER_USE_KEY); } + /** @return array */ protected function getHTTPQueryArray(): array { $properties = $this->getProperties(); - $properties = array_filter($properties, function ($value) { - return $value !== null; - }); + $properties = array_filter($properties, static fn ($value) => $value !== null); - return array_map(function ($value) { + return array_map(static function ($value) { if (\is_bool($value)) { return $value ? 'true' : 'false'; } + if ($value instanceof \BackedEnum) { + $value = $value->value; + } + return $value; }, $properties); } diff --git a/src/Parameters/CreateMeetingParameters.php b/src/Parameters/CreateMeetingParameters.php index 0afa4b0d..8e00d7ae 100644 --- a/src/Parameters/CreateMeetingParameters.php +++ b/src/Parameters/CreateMeetingParameters.php @@ -1,4 +1,7 @@ }> + */ + private array $breakoutRoomsGroups = []; + + /** + * @var array + */ + protected array $disabledFeatures = []; + + /** + * @var array + */ + protected array $disabledFeaturesExclude = []; + + protected ?int $meetingCameraCap = null; + protected ?int $meetingExpireIfNoUserJoinedInMinutes = null; + protected ?int $meetingExpireWhenLastUserLeftInMinutes = null; + protected ?bool $preUploadedPresentationOverrideDefault = null; + protected ?string $preUploadedPresentation = null; + protected ?string $preUploadedPresentationName = null; + protected ?bool $notifyRecordingIsOn = null; + protected ?bool $remindRecordingIsOn = null; + protected ?bool $recordFullDurationMedia = null; + protected ?string $presentationUploadExternalUrl = null; + protected ?string $presentationUploadExternalDescription = null; + protected ?int $maxNumPages = null; + protected ?string $pluginManifests = null; + protected ?string $pluginManifestsFetchUrl = null; + protected ?bool $presentationConversionCacheEnabled = null; + protected ?bool $allowOverrideClientSettingsOnCreateCall = null; + protected ?string $clientSettingsOverride = null; + + /** + * @var array + */ + private array $presentations = []; + + public function __construct(protected string $meetingID, protected string $name) { - $this->meetingID = $meetingID; - $this->name = $name; + $this->guestPolicy = GuestPolicy::ALWAYS_ACCEPT; + + $this->ignoreProperties = ['disabledFeatures', 'disabledFeaturesExclude', 'clientSettingsOverride']; } public function setEndCallbackUrl(string $endCallbackUrl): self @@ -487,25 +362,69 @@ public function addPresentation(string $nameOrUrl, ?string $content = null, ?str return $this; } - public function getPresentations(): array + /** + * @return array}> + */ + public function getBreakoutRoomsGroups(): array { - return $this->presentations; + return $this->breakoutRoomsGroups; } /** - * @return mixed + * @param array $roster + * + * @return $this */ - public function getPresentationsAsXML() + public function addBreakoutRoomsGroup(string $id, ?string $name, array $roster): self { - $result = ''; + $this->breakoutRoomsGroups[] = ['id' => $id, 'name' => $name, 'roster' => $roster]; + + return $this; + } + /** @return array */ + public function getPresentations(): array + { + return $this->presentations; + } + + public function getModules(): string + { + $xml = new SimpleXMLElementExtended(''); + // Get empty xml as string + $emptyXML = $xml->asXML(); + + // Add modules + $this->addPresentationsModule($xml); + $this->addClientSettingsOverrideModule($xml); + + // Get xml as string after modules have been added + $resultXML = $xml->asXML(); + + // If xml was not modified (no modules added), return an empty string + if ($emptyXML === $resultXML) { + return ''; + } + + return $resultXML; + } + + public function addClientSettingsOverrideModule(SimpleXMLElementExtended $xml): void + { + if (!empty($this->clientSettingsOverride)) { + $module = $xml->addChildWithCData('module', $this->clientSettingsOverride); + $module->addAttribute('name', 'clientSettingsOverride'); + } + } + + public function addPresentationsModule(SimpleXMLElementExtended $xml): void + { if (!empty($this->presentations)) { - $xml = new \SimpleXMLElement(''); $module = $xml->addChild('module'); $module->addAttribute('name', 'presentation'); foreach ($this->presentations as $nameOrUrl => $content) { - if (strpos($nameOrUrl, 'http') === 0) { + if (str_starts_with($nameOrUrl, 'http')) { $presentation = $module->addChild('document'); $presentation->addAttribute('url', $nameOrUrl); if (\is_string($content)) { @@ -514,22 +433,41 @@ public function getPresentationsAsXML() } else { $document = $module->addChild('document'); $document->addAttribute('name', $nameOrUrl); + /* @phpstan-ignore-next-line */ $document[0] = $content; } } - $result = $xml->asXML(); } - - return $result; } public function getHTTPQuery(): string { $queries = $this->getHTTPQueryArray(); + // Add disabled features if any are set + if (!empty($this->disabledFeatures)) { + $queries = array_merge($queries, [ + 'disabledFeatures' => implode(',', array_map(static fn (Feature $disabledFeature): string => $disabledFeature->value, $this->disabledFeatures)), + ]); + } + + // Add disabled features exclude if any are set + if (!empty($this->disabledFeaturesExclude)) { + $queries = array_merge($queries, [ + 'disabledFeaturesExclude' => implode(',', array_map(static fn (Feature $disabledFeatureExclude): string => $disabledFeatureExclude->value, $this->disabledFeaturesExclude)), + ]); + } + + // Pre-defined groups to automatically assign the students to a given breakout room + if (!empty($this->breakoutRoomsGroups)) { + $queries = array_merge($queries, [ + 'groups' => json_encode($this->breakoutRoomsGroups), + ]); + } + if ($this->isBreakout()) { if ($this->parentMeetingID === null || $this->sequence === null) { - trigger_error('Breakout rooms require a parentMeetingID and sequence number.', \E_USER_WARNING); + throw new \RuntimeException('Breakout rooms require a parentMeetingID and sequence number.'); } } else { $queries = $this->filterBreakoutRelatedQueries($queries); @@ -538,10 +476,13 @@ public function getHTTPQuery(): string return http_build_query($queries, '', '&', \PHP_QUERY_RFC3986); } + /** + * @param array $queries + * + * @return array + */ private function filterBreakoutRelatedQueries(array $queries): array { - return array_filter($queries, function ($query) { - return !\in_array($query, ['isBreakout', 'parentMeetingID', 'sequence', 'freeJoin']); - }); + return array_filter($queries, static fn ($query) => !\in_array($query, ['isBreakout', 'parentMeetingID', 'sequence', 'freeJoin'])); } } diff --git a/src/Parameters/DeleteRecordingsParameters.php b/src/Parameters/DeleteRecordingsParameters.php index f5d46b8a..3754105d 100644 --- a/src/Parameters/DeleteRecordingsParameters.php +++ b/src/Parameters/DeleteRecordingsParameters.php @@ -1,4 +1,7 @@ recordID = $recordID; } } diff --git a/src/Parameters/EndMeetingParameters.php b/src/Parameters/EndMeetingParameters.php index 82b19f31..918f5c81 100644 --- a/src/Parameters/EndMeetingParameters.php +++ b/src/Parameters/EndMeetingParameters.php @@ -1,4 +1,7 @@ password = $password; - $this->meetingID = $meetingID; } } diff --git a/src/Parameters/GetMeetingInfoParameters.php b/src/Parameters/GetMeetingInfoParameters.php index c6fe1a18..c11e1b2e 100644 --- a/src/Parameters/GetMeetingInfoParameters.php +++ b/src/Parameters/GetMeetingInfoParameters.php @@ -1,4 +1,7 @@ meetingID = $meetingID; } } diff --git a/src/Parameters/GetRecordingTextTracksParameters.php b/src/Parameters/GetRecordingTextTracksParameters.php index c7c0443b..59cbf83b 100644 --- a/src/Parameters/GetRecordingTextTracksParameters.php +++ b/src/Parameters/GetRecordingTextTracksParameters.php @@ -1,4 +1,7 @@ recordID = $recordID; } } diff --git a/src/Parameters/GetRecordingsParameters.php b/src/Parameters/GetRecordingsParameters.php index 745ee36b..38cb842c 100644 --- a/src/Parameters/GetRecordingsParameters.php +++ b/src/Parameters/GetRecordingsParameters.php @@ -1,4 +1,7 @@ callbackURL = $callbackURL; } } diff --git a/src/Parameters/HooksDestroyParameters.php b/src/Parameters/HooksDestroyParameters.php index c22e953c..f1134de9 100644 --- a/src/Parameters/HooksDestroyParameters.php +++ b/src/Parameters/HooksDestroyParameters.php @@ -1,4 +1,7 @@ hookID = $hookID; } } diff --git a/src/Parameters/HooksListParameters.php b/src/Parameters/HooksListParameters.php new file mode 100644 index 00000000..0df6305c --- /dev/null +++ b/src/Parameters/HooksListParameters.php @@ -0,0 +1,34 @@ +. + */ + +namespace BigBlueButton\Parameters; + +/** + * Class HooksListParameters. + * + * @method string getMeetingID() + * @method $this setMeetingID(string $id) + */ +final class HooksListParameters extends MetaParameters +{ + protected ?string $meetingID = null; +} diff --git a/src/Parameters/InsertDocumentParameters.php b/src/Parameters/InsertDocumentParameters.php index 3542dd9b..538266e6 100644 --- a/src/Parameters/InsertDocumentParameters.php +++ b/src/Parameters/InsertDocumentParameters.php @@ -27,19 +27,11 @@ */ final class InsertDocumentParameters extends MetaParameters { - /** - * @var string - */ - protected $meetingID; + /** @var array */ + private array $presentations = []; - /** - * @var array - */ - private $presentations = []; - - public function __construct(string $meetingID) + public function __construct(protected string $meetingID) { - $this->meetingID = $meetingID; } public function addPresentation(string $url, string $filename, ?bool $downloadable = null, ?bool $removable = null): self @@ -60,10 +52,7 @@ public function removePresentation(string $url): self return $this; } - /** - * @return mixed - */ - public function getPresentationsAsXML() + public function getPresentationsAsXML(): string|false { $result = ''; diff --git a/src/Parameters/IsMeetingRunningParameters.php b/src/Parameters/IsMeetingRunningParameters.php index 9c87923c..9244f666 100644 --- a/src/Parameters/IsMeetingRunningParameters.php +++ b/src/Parameters/IsMeetingRunningParameters.php @@ -1,4 +1,7 @@ meetingID = $meetingID; } } diff --git a/src/Parameters/JoinMeetingParameters.php b/src/Parameters/JoinMeetingParameters.php index 4a9dbc4a..a95eb384 100644 --- a/src/Parameters/JoinMeetingParameters.php +++ b/src/Parameters/JoinMeetingParameters.php @@ -1,4 +1,7 @@ meetingID = $meetingID; - $this->fullName = $fullName; - $this->password = $password; } } diff --git a/src/Parameters/MetaParameters.php b/src/Parameters/MetaParameters.php index 50ae4910..2adab816 100644 --- a/src/Parameters/MetaParameters.php +++ b/src/Parameters/MetaParameters.php @@ -1,4 +1,7 @@ */ + private array $meta = []; - /** - * @return string|bool - */ - public function getMeta(string $key) + public function getMeta(string $key): string|bool { return $this->meta[$key]; } - /** - * @param string|bool $value - */ - public function addMeta(string $key, $value): self + public function addMeta(string $key, bool|string $value): static { $this->meta[$key] = $value; diff --git a/src/Parameters/PublishRecordingsParameters.php b/src/Parameters/PublishRecordingsParameters.php index d003b2ba..fd73ef0d 100644 --- a/src/Parameters/PublishRecordingsParameters.php +++ b/src/Parameters/PublishRecordingsParameters.php @@ -1,4 +1,7 @@ recordID = $recordID; - $this->publish = $publish; } } diff --git a/src/Parameters/PutRecordingTextTrackParameters.php b/src/Parameters/PutRecordingTextTrackParameters.php index 728ac032..7969bcbc 100644 --- a/src/Parameters/PutRecordingTextTrackParameters.php +++ b/src/Parameters/PutRecordingTextTrackParameters.php @@ -1,4 +1,7 @@ ignoreProperties = ['contentType', 'file']; - - $this->recordID = $recordID; - $this->kind = $kind; - $this->lang = $lang; - $this->label = $label; } } diff --git a/src/Parameters/SendChatMessageParameters.php b/src/Parameters/SendChatMessageParameters.php new file mode 100644 index 00000000..3fb30f97 --- /dev/null +++ b/src/Parameters/SendChatMessageParameters.php @@ -0,0 +1,39 @@ +. + */ + +namespace BigBlueButton\Parameters; + +/** + * Class SendChatMessageParameters. + * + * @method string getMeetingID() + * @method $this setMeetingID(string $id) + * @method string getMessage() + * @method $this setMessage(string $message) + * @method string|null getUserName() + * @method $this setUserName(string $userName) + */ +final class SendChatMessageParameters extends BaseParameters +{ + public function __construct(protected string $meetingID, protected string $message, protected ?string $userName = null) + { + } +} diff --git a/src/Parameters/UpdateRecordingsParameters.php b/src/Parameters/UpdateRecordingsParameters.php index 65c8ec47..1dc2f2a6 100644 --- a/src/Parameters/UpdateRecordingsParameters.php +++ b/src/Parameters/UpdateRecordingsParameters.php @@ -1,4 +1,7 @@ recordID = $recordID; } } diff --git a/src/Parameters/UserDataParameters.php b/src/Parameters/UserDataParameters.php index a412a335..a1f788f0 100644 --- a/src/Parameters/UserDataParameters.php +++ b/src/Parameters/UserDataParameters.php @@ -1,4 +1,7 @@ */ + private array $userData = []; - /** - * @return string|bool - */ - public function getUserData(string $key) + public function getUserData(string $key): bool|string { return $this->userData[$key]; } - /** - * @param string|bool $value - */ - public function addUserData(string $key, $value): self + public function addUserData(string $key, bool|string $value): self { $this->userData[$key] = $value; diff --git a/src/Responses/ApiVersionResponse.php b/src/Responses/ApiVersionResponse.php index f747eefd..bbe86ed9 100644 --- a/src/Responses/ApiVersionResponse.php +++ b/src/Responses/ApiVersionResponse.php @@ -1,4 +1,7 @@ rawXml = $xml; } public function getRawXml(): \SimpleXMLElement @@ -61,12 +58,12 @@ public function getMessage(): string return $this->rawXml->message->__toString(); } - public function success() + public function success(): bool { return $this->getReturnCode() === self::SUCCESS; } - public function failed() + public function failed(): bool { return $this->getReturnCode() === self::FAILED; } @@ -76,6 +73,6 @@ public function failed() */ public function hasChecksumError(): bool { - return $this->failed() && $this->getMessageKey() == self::CHECKSUM_ERROR; + return $this->failed() && $this->getMessageKey() === self::CHECKSUM_ERROR; } } diff --git a/src/Responses/BaseResponseAsJson.php b/src/Responses/BaseResponseAsJson.php index 7d45bba9..77b8fa56 100644 --- a/src/Responses/BaseResponseAsJson.php +++ b/src/Responses/BaseResponseAsJson.php @@ -1,4 +1,7 @@ data); } + /** + * @return array + */ public function getRawArray(): array { return json_decode(json_encode($this->data), true); } - public function getMessage(): ?string + public function getMessage(): string { - if ($this->failed()) { - return $this->data->response->message; - } - - return null; + return $this->data->response->message ?? ''; } - public function getMessageKey(): ?string + public function getMessageKey(): string { - if ($this->failed()) { - return $this->data->response->messageKey; - } - - return null; + return $this->data->response->messageKey ?? ''; } public function getReturnCode(): string { - return $this->data->response->returncode; + return $this->data->response->returncode ?? ''; } public function success(): bool diff --git a/src/Responses/CreateMeetingResponse.php b/src/Responses/CreateMeetingResponse.php index 89b063fb..2c5f350a 100644 --- a/src/Responses/CreateMeetingResponse.php +++ b/src/Responses/CreateMeetingResponse.php @@ -1,4 +1,7 @@ rawXml->parentMeetingID->__toString(); } - public function getAttendeePassword(): string - { - return $this->rawXml->attendeePW->__toString(); - } - - public function getModeratorPassword(): string - { - return $this->rawXml->moderatorPW->__toString(); - } - /** * Creation timestamp. * diff --git a/src/Responses/DeleteRecordingsResponse.php b/src/Responses/DeleteRecordingsResponse.php index 9cba904c..2df97a4a 100644 --- a/src/Responses/DeleteRecordingsResponse.php +++ b/src/Responses/DeleteRecordingsResponse.php @@ -1,4 +1,7 @@ rawXml->deleted->__toString() == 'true'; } - public function isInvalidId(): bool + public function isNotFound(): bool { - return $this->getMessageKey() === self::KEY_INVALID_ID; + return $this->getMessageKey() === self::KEY_NOT_FOUND; } } diff --git a/src/Responses/EndMeetingResponse.php b/src/Responses/EndMeetingResponse.php index 03565545..a1b8e283 100644 --- a/src/Responses/EndMeetingResponse.php +++ b/src/Responses/EndMeetingResponse.php @@ -1,4 +1,7 @@ rawXml->removed->__toString() === 'true'; } + + public function isMissingHook(): bool + { + return $this->getMessageKey() === self::KEY_MISSING_HOOK; + } + + public function isHookError(): bool + { + return $this->getMessageKey() === self::KEY_HOOK_ERROR; + } } diff --git a/src/Responses/HooksListResponse.php b/src/Responses/HooksListResponse.php index 0441962f..4edb0ee5 100644 --- a/src/Responses/HooksListResponse.php +++ b/src/Responses/HooksListResponse.php @@ -1,4 +1,7 @@ rawXml->published->__toString() === 'true'; } + + public function isNotFound(): bool + { + return $this->getMessageKey() === self::KEY_NOT_FOUND; + } } diff --git a/src/Responses/PutRecordingTextTrackResponse.php b/src/Responses/PutRecordingTextTrackResponse.php index b512a635..27aa1cf0 100644 --- a/src/Responses/PutRecordingTextTrackResponse.php +++ b/src/Responses/PutRecordingTextTrackResponse.php @@ -1,4 +1,7 @@ data->response->recordId; + return $this->data->response->recordId ?? ''; } public function isUploadTrackSuccess(): bool @@ -49,6 +55,21 @@ public function isUploadTrackEmpty(): bool return $this->getMessageKey() === self::KEY_EMPTY; } + public function isNoRecordings(): bool + { + return $this->getMessageKey() === self::KEY_NO_RECORDINGS; + } + + public function isInvalidLang(): bool + { + return $this->getMessageKey() === self::KEY_INVALID_LANG; + } + + public function isInvalidKind(): bool + { + return $this->getMessageKey() === self::KEY_INVALID_KIND; + } + public function isKeyParamError(): bool { return $this->getMessageKey() === self::KEY_PARAM_ERROR; diff --git a/src/Responses/SendChatMessageResponse.php b/src/Responses/SendChatMessageResponse.php new file mode 100644 index 00000000..95ee4ebd --- /dev/null +++ b/src/Responses/SendChatMessageResponse.php @@ -0,0 +1,26 @@ +. + */ + +namespace BigBlueButton\Responses; + +final class SendChatMessageResponse extends BaseResponse +{ +} diff --git a/src/Responses/UpdateRecordingsResponse.php b/src/Responses/UpdateRecordingsResponse.php index c30ceaef..acbc7271 100644 --- a/src/Responses/UpdateRecordingsResponse.php +++ b/src/Responses/UpdateRecordingsResponse.php @@ -1,4 +1,7 @@ rawXml->updated->__toString() === 'true'; } + + public function isNotFound(): bool + { + return $this->getMessageKey() === self::KEY_NOT_FOUND; + } } diff --git a/src/Util/ArrayHelper.php b/src/Util/ArrayHelper.php index a4d3d930..140e7119 100644 --- a/src/Util/ArrayHelper.php +++ b/src/Util/ArrayHelper.php @@ -31,7 +31,10 @@ final class ArrayHelper * * @see https://www.php.net/manual/en/function.array-merge-recursive.php * - * @param bool $reorderNested reorder nested array starting from the second level instead of merging them + * @param array $arrays + * @param bool $reorderNested reorder nested array starting from the second level instead of merging them + * + * @return array */ public static function mergeRecursive(bool $reorderNested, array ...$arrays): array { @@ -51,6 +54,12 @@ public static function mergeRecursive(bool $reorderNested, array ...$arrays): ar return $merged; } + /** + * @param array $array1 + * @param array $array2 + * + * @return array + */ private static function mergeArrays(array $array1, array $array2): array { $newArray = []; diff --git a/src/Util/SimpleXMLElementExtended.php b/src/Util/SimpleXMLElementExtended.php new file mode 100644 index 00000000..d78c9e20 --- /dev/null +++ b/src/Util/SimpleXMLElementExtended.php @@ -0,0 +1,24 @@ +ownerDocument; + $element->appendChild($docOwner->createCDATASection($value)); + + return $child; + } +} diff --git a/src/Util/UrlBuilder.php b/src/Util/UrlBuilder.php index f2b58b8c..822e04a9 100644 --- a/src/Util/UrlBuilder.php +++ b/src/Util/UrlBuilder.php @@ -1,4 +1,7 @@ securitySalt = $secret; - $this->bbbServerBaseUrl = $serverBaseUrl; + public function __construct( + private readonly string $securitySalt, + private readonly string $bbbServerBaseUrl, + private readonly HashingAlgorithm $hashingAlgorithm, + ) { } /** * Builds an API method URL that includes the url + params + its generated checksum. */ - public function buildUrl(string $method = '', string $params = '', bool $append = true): string + public function buildUrl(string|ApiMethod $method = '', string $params = '', bool $append = true): string { + if ($method instanceof ApiMethod) { + $method = $method->value; + } + return $this->bbbServerBaseUrl.'api/'.$method.($append ? '?'.$this->buildQs($method, $params) : ''); } @@ -61,6 +63,6 @@ public function buildQs(string $method = '', string $params = ''): string $checksumParam = 'checksum='; } - return $params.$checksumParam.sha1($method.$params.$this->securitySalt); + return $params.$checksumParam.hash($this->hashingAlgorithm->value, $method.$params.$this->securitySalt); } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 64a63992..d22a1a3c 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,4 +1,7 @@ load(...$files); } - -// Include custom test class -require_once __DIR__.'/TestCase.php'; diff --git a/tests/TestCase.php b/tests/common/TestCase.php similarity index 59% rename from tests/TestCase.php rename to tests/common/TestCase.php index d66e4bb0..19c792fc 100644 --- a/tests/TestCase.php +++ b/tests/common/TestCase.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton; +namespace BigBlueButton\Tests\Common; -use BigBlueButton\Core\GuestPolicy; -use BigBlueButton\Core\MeetingLayout; +use BigBlueButton\BigBlueButton; +use BigBlueButton\Enum\Feature; +use BigBlueButton\Enum\GuestPolicy; +use BigBlueButton\Enum\MeetingLayout; +use BigBlueButton\Enum\Role; use BigBlueButton\Parameters\CreateMeetingParameters; use BigBlueButton\Parameters\EndMeetingParameters; use BigBlueButton\Parameters\JoinMeetingParameters; @@ -33,29 +39,16 @@ /** * Class TestCase. */ -class TestCase extends \PHPUnit\Framework\TestCase +abstract class TestCase extends \PHPUnit\Framework\TestCase { - /** - * @var Generator - */ - protected $faker; + protected Generator $faker; - /** - * {@inheritdoc} - */ protected function setUp(): void { - parent::setUp(); - $this->faker = Faker::create(); } - /** - * @param $bbb BigBlueButton - * - * @return CreateMeetingResponse - */ - protected function createRealMeeting($bbb) + protected function createRealMeeting(BigBlueButton $bbb): CreateMeetingResponse { $createMeetingParams = $this->generateCreateParams(); $createMeetingMock = $this->getCreateMock($createMeetingParams); @@ -63,19 +56,15 @@ protected function createRealMeeting($bbb) return $bbb->createMeeting($createMeetingMock); } - /** - * @return array - */ - protected function generateCreateParams() + /** @return array */ + protected function generateCreateParams(): array { return [ 'name' => $this->faker->name, 'meetingID' => $this->faker->uuid, - 'attendeePW' => $this->faker->password, - 'moderatorPW' => $this->faker->password, 'autoStartRecording' => $this->faker->boolean(50), 'dialNumber' => $this->faker->phoneNumber, - 'voiceBridge' => $this->faker->randomNumber(5), + 'voiceBridge' => $this->faker->randomNumber(5, true), 'webVoice' => $this->faker->word, 'logoutURL' => $this->faker->url, 'maxParticipants' => $this->faker->numberBetween(2, 100), @@ -86,6 +75,7 @@ protected function generateCreateParams() 'moderatorOnlyMessage' => $this->faker->sentence, 'webcamsOnlyForModerator' => $this->faker->boolean(50), 'logo' => $this->faker->imageUrl(330, 70), + 'darklogo' => $this->faker->imageUrl(330, 70), 'copyright' => $this->faker->text, 'guestPolicy' => $this->faker->randomElement([GuestPolicy::ALWAYS_ACCEPT, GuestPolicy::ALWAYS_DENY, GuestPolicy::ASK_MODERATOR]), 'muteOnStart' => $this->faker->boolean(50), @@ -93,28 +83,30 @@ protected function generateCreateParams() 'lockSettingsDisableMic' => $this->faker->boolean(50), 'lockSettingsDisablePrivateChat' => $this->faker->boolean(50), 'lockSettingsDisablePublicChat' => $this->faker->boolean(50), - 'lockSettingsDisableNote' => $this->faker->boolean(50), + 'lockSettingsDisableNotes' => $this->faker->boolean(50), 'lockSettingsHideUserList' => $this->faker->boolean(50), 'lockSettingsLockedLayout' => $this->faker->boolean(50), 'lockSettingsLockOnJoin' => $this->faker->boolean(50), 'lockSettingsLockOnJoinConfigurable' => $this->faker->boolean(50), 'allowModsToUnmuteUsers' => $this->faker->boolean(50), 'allowModsToEjectCameras' => $this->faker->boolean(50), + 'disabledFeatures' => $this->faker->randomElements(Feature::cases(), 3), + 'disabledFeaturesExclude' => $this->faker->randomElements(Feature::cases(), 2), + 'allowPromoteGuestToModerator' => $this->faker->boolean(50), 'meta_presenter' => $this->faker->name, 'meta_endCallbackUrl' => $this->faker->url, 'meta_bbb-recording-ready-url' => $this->faker->url, 'bannerText' => $this->faker->sentence, - 'bannerColor' => $this->faker->hexcolor, + 'bannerColor' => $this->faker->hexColor, 'meetingKeepEvents' => $this->faker->boolean(50), 'endWhenNoModerator' => $this->faker->boolean(50), 'endWhenNoModeratorDelayInMinutes' => $this->faker->numberBetween(1, 100), 'meetingLayout' => $this->faker->randomElement([ - MeetingLayout::CUSTOM_LAYOUT, - MeetingLayout::SMART_LAYOUT, - MeetingLayout::PRESENTATION_FOCUS, - MeetingLayout::VIDEO_FOCUS, - ]), - 'learningDashboardEnabled' => $this->faker->boolean(50), + MeetingLayout::CUSTOM_LAYOUT, + MeetingLayout::SMART_LAYOUT, + MeetingLayout::PRESENTATION_FOCUS, + MeetingLayout::VIDEO_FOCUS, + ]), 'learningDashboardCleanupDelayInMinutes' => $this->faker->numberBetween(1, 100), 'breakoutRoomsEnabled' => $this->faker->boolean(50), 'breakoutRoomsPrivateChatEnabled' => $this->faker->boolean(50), @@ -123,15 +115,30 @@ protected function generateCreateParams() 'allowRequestsWithoutSession' => $this->faker->boolean(50), 'virtualBackgroundsDisabled' => $this->faker->boolean(50), 'userCameraCap' => $this->faker->numberBetween(1, 5), + 'groups' => $this->generateBreakoutRoomsGroups(), ]; } /** - * @param $createParams + * @return array}> + */ + protected function generateBreakoutRoomsGroups(): array + { + $br = $this->faker->numberBetween(0, 8); + $groups = []; + for ($i = 0; $i <= $br; ++$i) { + $groups[] = ['id' => $this->faker->uuid, 'name' => $this->faker->name, 'roster' => $this->faker->randomElements]; + } + + return $groups; + } + + /** + * @param array $createParams * - * @return array + * @return array */ - protected function generateBreakoutCreateParams($createParams) + protected function generateBreakoutCreateParams(array $createParams): array { return array_merge($createParams, [ 'isBreakout' => true, @@ -141,18 +148,12 @@ protected function generateBreakoutCreateParams($createParams) ]); } - /** - * @param $params array - * - * @return CreateMeetingParameters - */ - protected function getCreateMock($params) + /** @param array $params */ + protected function getCreateMock(array $params): CreateMeetingParameters { $createMeetingParams = new CreateMeetingParameters($params['meetingID'], $params['name']); - return $createMeetingParams->setAttendeePW($params['attendeePW']) - ->setModeratorPW($params['moderatorPW']) - ->setDialNumber($params['dialNumber']) + $createMeetingParams->setDialNumber($params['dialNumber']) ->setVoiceBridge($params['voiceBridge']) ->setWebVoice($params['webVoice']) ->setLogoutURL($params['logoutURL']) @@ -165,6 +166,7 @@ protected function getCreateMock($params) ->setModeratorOnlyMessage($params['moderatorOnlyMessage']) ->setWebcamsOnlyForModerator($params['webcamsOnlyForModerator']) ->setLogo($params['logo']) + ->setDarklogo($params['darklogo']) ->setCopyright($params['copyright']) ->setEndCallbackUrl($params['meta_endCallbackUrl']) ->setRecordingReadyCallbackUrl($params['meta_bbb-recording-ready-url']) @@ -173,7 +175,7 @@ protected function getCreateMock($params) ->setLockSettingsDisableMic($params['lockSettingsDisableMic']) ->setLockSettingsDisablePrivateChat($params['lockSettingsDisablePrivateChat']) ->setLockSettingsDisablePublicChat($params['lockSettingsDisablePublicChat']) - ->setLockSettingsDisableNote($params['lockSettingsDisableNote']) + ->setLockSettingsDisableNotes($params['lockSettingsDisableNotes']) ->setLockSettingsHideUserList($params['lockSettingsHideUserList']) ->setLockSettingsLockedLayout($params['lockSettingsLockedLayout']) ->setLockSettingsLockOnJoin($params['lockSettingsLockOnJoin']) @@ -190,86 +192,79 @@ protected function getCreateMock($params) ->setMeetingEndedURL($params['meetingEndedURL']) ->setMeetingLayout($params['meetingLayout']) ->setMeetingKeepEvents($params['meetingKeepEvents']) - ->setLearningDashboardEnabled($params['learningDashboardEnabled']) ->setLearningDashboardCleanupDelayInMinutes($params['learningDashboardCleanupDelayInMinutes']) ->setAllowModsToEjectCameras($params['allowModsToEjectCameras']) - ->setBreakoutRoomsEnabled($params['breakoutRoomsEnabled']) ->setBreakoutRoomsPrivateChatEnabled($params['breakoutRoomsPrivateChatEnabled']) ->setBreakoutRoomsRecord($params['breakoutRoomsRecord']) ->setAllowRequestsWithoutSession($params['allowRequestsWithoutSession']) - ->setVirtualBackgroundsDisabled($params['virtualBackgroundsDisabled']) - ->setUserCameraCap($params['userCameraCap']); + ->setAllowPromoteGuestToModerator($params['allowPromoteGuestToModerator']) + ->setUserCameraCap($params['userCameraCap']) + ->setDisabledFeatures($params['disabledFeatures']) + ->setDisabledFeaturesExclude($params['disabledFeaturesExclude']); + + foreach ($params['groups'] as $group) { + $createMeetingParams->addBreakoutRoomsGroup($group['id'], $group['name'], $group['roster']); + } + + return $createMeetingParams; } - /** - * @param $params - * - * @return CreateMeetingParameters - */ - protected function getBreakoutCreateMock($params) + /** @param array $params */ + protected function getBreakoutCreateMock(array $params): CreateMeetingParameters { $createMeetingParams = $this->getCreateMock($params); - return $createMeetingParams->setBreakout($params['isBreakout'])->setParentMeetingID($params['parentMeetingId'])-> - setSequence($params['sequence'])->setFreeJoin($params['freeJoin']); + return $createMeetingParams->setBreakout($params['isBreakout'])->setParentMeetingID($params['parentMeetingId'])->setSequence($params['sequence'])->setFreeJoin($params['freeJoin']); } - /** - * @return array - */ - protected function generateJoinMeetingParams() + /** @return array */ + protected function generateJoinMeetingParams(): array { return ['meetingID' => $this->faker->uuid, - 'fullName' => $this->faker->name, - 'password' => $this->faker->password, - 'userID' => $this->faker->numberBetween(1, 1000), - 'webVoiceConf' => $this->faker->word, - 'createTime' => $this->faker->unixTime, - 'userdata-countrycode' => $this->faker->countryCode, - 'userdata-email' => $this->faker->email, - 'userdata-commercial' => false, + 'fullName' => $this->faker->name, + 'role' => $this->faker->randomElement(Role::cases()), + 'userID' => (string) $this->faker->numberBetween(1, 1000), + 'webVoiceConf' => $this->faker->word, + 'createTime' => $this->faker->unixTime, + 'errorRedirectUrl' => $this->faker->url, + 'userdata-countrycode' => $this->faker->countryCode, + 'userdata-email' => $this->faker->email, + 'userdata-commercial' => false, ]; } - /** - * @param $params array - * - * @return JoinMeetingParameters - */ - protected function getJoinMeetingMock($params) + /** @param array $params */ + protected function getJoinMeetingMock(array $params): JoinMeetingParameters { - $joinMeetingParams = new JoinMeetingParameters($params['meetingID'], $params['fullName'], $params['password']); - - return $joinMeetingParams->setUserID($params['userID'])->setWebVoiceConf($params['webVoiceConf']) - ->setCreateTime($params['createTime'])->addUserData('countrycode', $params['userdata-countrycode']) - ->addUserData('email', $params['userdata-email'])->addUserData('commercial', $params['userdata-commercial']); + $joinMeetingParams = new JoinMeetingParameters($params['meetingID'], $params['fullName'], $params['role']); + + $joinMeetingParams + ->setUserID($params['userID']) + ->setWebVoiceConf($params['webVoiceConf']) + ->setCreateTime($params['createTime']) + ->setErrorRedirectUrl($params['errorRedirectUrl']) + ->addUserData('countrycode', $params['userdata-countrycode']) + ->addUserData('email', $params['userdata-email']) + ->addUserData('commercial', $params['userdata-commercial']); + + return $joinMeetingParams; } - /** - * @return array - */ - protected function generateEndMeetingParams() + /** @return array */ + protected function generateEndMeetingParams(): array { - return ['meetingID' => $this->faker->uuid, - 'password' => $this->faker->password, ]; + return [ + 'meetingID' => $this->faker->uuid, + ]; } - /** - * @param $params array - * - * @return EndMeetingParameters - */ - protected function getEndMeetingMock($params) + /** @param array $params */ + protected function getEndMeetingMock(array $params): EndMeetingParameters { - return new EndMeetingParameters($params['meetingID'], $params['password']); + return new EndMeetingParameters($params['meetingID']); } - /** - * @param $bbb BigBlueButton - * - * @return UpdateRecordingsResponse - */ - protected function updateRecordings($bbb) + protected function updateRecordings(BigBlueButton $bbb): UpdateRecordingsResponse { $updateRecordingsParams = $this->generateUpdateRecordingsParams(); $updateRecordingsMock = $this->getUpdateRecordingsParamsMock($updateRecordingsParams); @@ -277,10 +272,8 @@ protected function updateRecordings($bbb) return $bbb->updateRecordings($updateRecordingsMock); } - /** - * @return array - */ - protected function generateUpdateRecordingsParams() + /** @return array */ + protected function generateUpdateRecordingsParams(): array { return [ 'recordID' => $this->faker->uuid, @@ -288,62 +281,91 @@ protected function generateUpdateRecordingsParams() ]; } - /** - * @param $params array - * - * @return UpdateRecordingsParameters - */ - protected function getUpdateRecordingsParamsMock($params) + /** @param array $params */ + protected function getUpdateRecordingsParamsMock(array $params): UpdateRecordingsParameters { - $updateRecordingsParams = new UpdateRecordingsParameters($params['recordID']); + $updateRecordingParameters = new UpdateRecordingsParameters($params['recordID']); + $updateRecordingParameters->addMeta('presenter', $params['meta_presenter']); - return $updateRecordingsParams->addMeta('presenter', $params['meta_presenter']); + return $updateRecordingParameters; } // Load fixtures - protected function loadXmlFile($path) + protected function loadXmlFile(string $path): \SimpleXMLElement { return simplexml_load_string(file_get_contents($path)); } - protected function loadJsonFile($path) + protected function loadJsonFile(string $path): string { return file_get_contents($path); } - protected function minifyString($string) + protected function minifyString(string $string): string { - return str_replace(["\r\n", "\r", "\n", "\t", ' '], '', $string); + return str_replace(["\r\n", "\r", "\n", "\t", ' '], '', (string) $string); } // Additional assertions - public function assertEachGetterValueIsString($obj, $getters) + /** + * @param array $getters + */ + public function assertEachGetterValueIsString(object $obj, array $getters): void { foreach ($getters as $getterName) { $this->assertIsString($obj->$getterName(), 'Got a '.\gettype($obj->$getterName()).' instead of a string for property -> '.$getterName); } } - public function assertEachGetterValueIsInteger($obj, $getters) + /** + * @param array $getters + */ + public function assertEachGetterValueIsInteger(object $obj, array $getters): void { foreach ($getters as $getterName) { $this->assertIsInt($obj->$getterName(), 'Got a '.\gettype($obj->$getterName()).' instead of an integer for property -> '.$getterName); } } - public function assertEachGetterValueIsDouble($obj, $getters) + /** + * @param array $getters + */ + public function assertEachGetterValueIsDouble(object $obj, array $getters): void { foreach ($getters as $getterName) { $this->assertIsFloat($obj->$getterName(), 'Got a '.\gettype($obj->$getterName()).' instead of a double for property -> '.$getterName); } } - public function assertEachGetterValueIsBoolean($obj, $getters) + /** + * @param array $getters + */ + public function assertEachGetterValueIsBoolean(object $obj, array $getters): void { foreach ($getters as $getterName) { $this->assertIsBool($obj->$getterName(), 'Got a '.\gettype($obj->$getterName()).' instead of a boolean for property -> '.$getterName); } } + + /** @param array $parameters */ + public function assertUrlContainsAllRequestParameters(string $url, array $parameters): void + { + foreach ($parameters as $parameter) { + if (\is_bool($parameter)) { + $parameter = $parameter ? 'true' : 'false'; + } + + if ($parameter instanceof \BackedEnum) { + $parameter = $parameter->value; + } + + if (!\is_array($parameter)) { + $this->assertStringContainsString((string) $parameter, urldecode($url)); + } else { + $this->assertUrlContainsAllRequestParameters($url, $parameter); + } + } + } } diff --git a/tests/fixtures/client_settings.xml b/tests/fixtures/client_settings.xml new file mode 100644 index 00000000..6ef5db4b --- /dev/null +++ b/tests/fixtures/client_settings.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/tests/fixtures/create_meeting_not_unique_error.xml b/tests/fixtures/create_meeting_not_unique_error.xml new file mode 100644 index 00000000..1575d544 --- /dev/null +++ b/tests/fixtures/create_meeting_not_unique_error.xml @@ -0,0 +1,5 @@ + + FAILED + idNotUnique + A meeting already exists with that meeting ID. Please use a different meeting ID. + diff --git a/tests/fixtures/not_found_error.xml b/tests/fixtures/not_found_error.xml new file mode 100644 index 00000000..4a5d46b7 --- /dev/null +++ b/tests/fixtures/not_found_error.xml @@ -0,0 +1,5 @@ + + FAILED + notFound + We could not find recordings + diff --git a/tests/functional/AbstractBigBlueButtonFunctionalTest.php b/tests/functional/AbstractBigBlueButtonFunctionalTest.php index fdceead1..074963ff 100644 --- a/tests/functional/AbstractBigBlueButtonFunctionalTest.php +++ b/tests/functional/AbstractBigBlueButtonFunctionalTest.php @@ -1,4 +1,7 @@ bbb->isConnectionWorking(); @@ -89,7 +88,7 @@ public function testIsConnectionWorking() /** * Test API version call. */ - public function testApiVersion() + public function testApiVersion(): void { $apiVersion = $this->bbb->getApiVersion(); $this->assertEquals('SUCCESS', $apiVersion->getReturnCode()); @@ -99,36 +98,23 @@ public function testApiVersion() /* Create Meeting */ - /** - * Test create meeting URL. - */ - public function testCreateMeetingUrl(): void - { - $params = $this->generateCreateParams(); - $url = $this->bbb->getCreateMeetingUrl($this->getCreateMock($params)); - foreach ($params as $key => $value) { - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } - $this->assertStringContainsString($key.'='.rawurlencode($value), $url); - } - } - /** * Test create meeting. */ - public function testCreateMeeting() + public function testCreateMeeting(): void { - $params = $this->generateCreateParams(); - $result = $this->bbb->createMeeting($this->getCreateMock($params)); + $result = $this->createRealMeeting($this->bbb); $this->assertEquals('SUCCESS', $result->getReturnCode()); $this->assertTrue($result->success()); + + // Cleanup + $this->bbb->endMeeting(new EndMeetingParameters($result->getMeetingId())); } /** * Test create meeting with a document URL. */ - public function testCreateMeetingWithDocumentUrl() + public function testCreateMeetingWithDocumentUrl(): void { $params = $this->getCreateMock($this->generateCreateParams()); $params->addPresentation('https://picsum.photos/3840/2160/?random'); @@ -138,12 +124,15 @@ public function testCreateMeetingWithDocumentUrl() $this->assertCount(1, $params->getPresentations()); $this->assertEquals('SUCCESS', $result->getReturnCode()); $this->assertTrue($result->success()); + + // Cleanup + $this->bbb->endMeeting(new EndMeetingParameters($result->getMeetingId())); } /** * Test create meeting with a document URL and filename. */ - public function testCreateMeetingWithDocumentUrlAndFileName() + public function testCreateMeetingWithDocumentUrlAndFileName(): void { $params = $this->getCreateMock($this->generateCreateParams()); $params->addPresentation('https://picsum.photos/3840/2160/?random', null, 'placeholder.png'); @@ -153,12 +142,15 @@ public function testCreateMeetingWithDocumentUrlAndFileName() $this->assertCount(1, $params->getPresentations()); $this->assertEquals('SUCCESS', $result->getReturnCode()); $this->assertTrue($result->success()); + + // Cleanup + $this->bbb->endMeeting(new EndMeetingParameters($result->getMeetingId())); } /** * Test create meeting with a document URL. */ - public function testCreateMeetingWithDocumentEmbedded() + public function testCreateMeetingWithDocumentEmbedded(): void { $params = $this->getCreateMock($this->generateCreateParams()); $params->addPresentation('bbb_logo.png', file_get_contents(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'bbb_logo.png')); @@ -168,12 +160,15 @@ public function testCreateMeetingWithDocumentEmbedded() $this->assertCount(1, $params->getPresentations()); $this->assertEquals('SUCCESS', $result->getReturnCode()); $this->assertTrue($result->success()); + + // Cleanup + $this->bbb->endMeeting(new EndMeetingParameters($result->getMeetingId())); } /** * Test create meeting with a multiple documents. */ - public function testCreateMeetingWithMultiDocument() + public function testCreateMeetingWithMultiDocument(): void { $params = $this->getCreateMock($this->generateCreateParams()); $params->addPresentation('https://picsum.photos/3840/2160/?random', null, 'presentation.png'); @@ -184,39 +179,25 @@ public function testCreateMeetingWithMultiDocument() $this->assertCount(2, $params->getPresentations()); $this->assertEquals('SUCCESS', $result->getReturnCode()); $this->assertTrue($result->success()); + + // Cleanup + $this->bbb->endMeeting(new EndMeetingParameters($result->getMeetingId())); } /* Join Meeting */ - /** - * Test create join meeting URL. - */ - public function testCreateJoinMeetingUrl(): void - { - $joinMeetingParams = $this->generateJoinMeetingParams(); - $joinMeetingMock = $this->getJoinMeetingMock($joinMeetingParams); - - $url = $this->bbb->getJoinMeetingURL($joinMeetingMock); - - foreach ($joinMeetingParams as $key => $value) { - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } - $this->assertStringContainsString('='.rawurlencode($value), $url); - } - } - - public function testJoinMeeting() + public function testJoinMeeting(): void { - $params = $this->generateCreateParams(); - $result = $this->bbb->createMeeting($this->getCreateMock($params)); + $params = $this->getCreateMock($this->generateCreateParams()); + $params->setGuestPolicy('ALWAYS_ACCEPT'); + $result = $this->bbb->createMeeting($params); $this->assertEquals('SUCCESS', $result->getReturnCode(), 'Create meeting'); $creationTime = $result->getCreationTime(); - $joinMeetingParams = $this->generateJoinMeetingParams(); - $joinMeetingParams = new JoinMeetingParameters($result->getMeetingId(), 'Foobar', $result->getAttendeePassword()); + $params = $this->generateJoinMeetingParams(); + $joinMeetingParams = new JoinMeetingParameters($result->getMeetingId(), $params['fullName'], $params['role']); $joinMeetingParams->setRedirect(false); - $joinMeetingParams->setCreateTime(sprintf('%.0f', $creationTime)); + $joinMeetingParams->setCreateTime((int) \sprintf('%.0f', $creationTime)); $joinMeeting = $this->bbb->joinMeeting($joinMeetingParams); $this->assertEquals('SUCCESS', $joinMeeting->getReturnCode(), 'Join meeting'); @@ -226,36 +207,24 @@ public function testJoinMeeting() $this->assertNotEmpty($joinMeeting->getSessionToken()); $this->assertNotEmpty($joinMeeting->getGuestStatus()); $this->assertNotEmpty($joinMeeting->getUrl()); + + // Cleanup + $this->bbb->endMeeting(new EndMeetingParameters($result->getMeetingId())); } /* End Meeting */ - /** - * Test generate end meeting URL. - */ - public function testCreateEndMeetingUrl(): void - { - $params = $this->generateEndMeetingParams(); - $url = $this->bbb->getEndMeetingURL($this->getEndMeetingMock($params)); - foreach ($params as $key => $value) { - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } - $this->assertStringContainsString('='.rawurlencode($value), $url); - } - } - - public function testEndMeeting() + public function testEndMeeting(): void { $meeting = $this->createRealMeeting($this->bbb); - $endMeeting = new EndMeetingParameters($meeting->getMeetingId(), $meeting->getModeratorPassword()); + $endMeeting = new EndMeetingParameters($meeting->getMeetingId()); $result = $this->bbb->endMeeting($endMeeting); $this->assertEquals('SUCCESS', $result->getReturnCode()); $this->assertTrue($result->success()); } - public function testEndNonExistingMeeting() + public function testEndNonExistingMeeting(): void { $params = $this->generateEndMeetingParams(); $result = $this->bbb->endMeeting($this->getEndMeetingMock($params)); @@ -265,7 +234,7 @@ public function testEndNonExistingMeeting() /* Is Meeting Running */ - public function testIsMeetingRunning() + public function testIsMeetingRunning(): void { $result = $this->bbb->isMeetingRunning(new IsMeetingRunningParameters($this->faker->uuid)); $this->assertEquals('SUCCESS', $result->getReturnCode()); @@ -275,16 +244,15 @@ public function testIsMeetingRunning() /* Get Meetings */ - public function testGetMeetingsUrl(): void + public function testGetMeetings(): void { - $url = $this->bbb->getMeetingsUrl(); - $this->assertStringContainsString(ApiMethod::GET_MEETINGS, $url); - } + $meeting = $this->createRealMeeting($this->bbb); - public function testGetMeetings() - { $result = $this->bbb->getMeetings(); $this->assertNotEmpty($result->getMeetings()); + + // Cleanup + $this->bbb->endMeeting(new EndMeetingParameters($meeting->getMeetingId())); } /* Get meeting info */ @@ -295,69 +263,45 @@ public function testGetMeetingInfoUrl(): void $url = $this->bbb->getMeetingInfoUrl(new GetMeetingInfoParameters($meeting->getMeetingId())); $this->assertStringContainsString('='.rawurlencode($meeting->getMeetingId()), $url); + + // Cleanup + $this->bbb->endMeeting(new EndMeetingParameters($meeting->getMeetingId())); } - public function testGetMeetingInfo() + public function testGetMeetingInfo(): void { $meeting = $this->createRealMeeting($this->bbb); - $result = $this->bbb->getMeetingInfo(new GetMeetingInfoParameters($meeting->getMeetingId(), $meeting->getModeratorPassword())); + $result = $this->bbb->getMeetingInfo(new GetMeetingInfoParameters($meeting->getMeetingId())); $this->assertEquals('SUCCESS', $result->getReturnCode()); $this->assertTrue($result->success()); - } - public function testGetRecordingsUrl(): void - { - $url = $this->bbb->getRecordingsUrl(new GetRecordingsParameters()); - $this->assertStringContainsString(ApiMethod::GET_RECORDINGS, $url); + // Cleanup + $this->bbb->endMeeting(new EndMeetingParameters($meeting->getMeetingId())); } - public function testGetRecordings() + public function testGetRecordings(): void { $result = $this->bbb->getRecordings(new GetRecordingsParameters()); $this->assertEquals('SUCCESS', $result->getReturnCode()); $this->assertTrue($result->success()); } - public function testPublishRecordingsUrl(): void - { - $url = $this->bbb->getPublishRecordingsUrl(new PublishRecordingsParameters($this->faker->sha1, true)); - $this->assertStringContainsString(ApiMethod::PUBLISH_RECORDINGS, $url); - } - - public function testPublishRecordings() + public function testPublishRecordings(): void { $result = $this->bbb->publishRecordings(new PublishRecordingsParameters('non-existing-id-'.$this->faker->sha1, true)); $this->assertEquals('FAILED', $result->getReturnCode()); $this->assertTrue($result->failed()); } - public function testDeleteRecordingsUrl(): void - { - $url = $this->bbb->getDeleteRecordingsUrl(new DeleteRecordingsParameters($this->faker->sha1)); - $this->assertStringContainsString(ApiMethod::DELETE_RECORDINGS, $url); - } - - public function testDeleteRecordings() + public function testDeleteRecordings(): void { $result = $this->bbb->deleteRecordings(new DeleteRecordingsParameters('non-existing-id-'.$this->faker->sha1)); $this->assertEquals('FAILED', $result->getReturnCode()); $this->assertTrue($result->failed()); } - public function testUpdateRecordingsUrl(): void - { - $params = $this->generateUpdateRecordingsParams(); - $url = $this->bbb->getUpdateRecordingsUrl($this->getUpdateRecordingsParamsMock($params)); - foreach ($params as $key => $value) { - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } - $this->assertStringContainsString('='.rawurlencode($value), $url); - } - } - - public function testUpdateRecordings() + public function testUpdateRecordings(): void { $params = $this->generateUpdateRecordingsParams(); $result = $this->bbb->updateRecordings($this->getUpdateRecordingsParamsMock($params)); diff --git a/tests/functional/BigBlueButtonWithCurlTransportTest.php b/tests/functional/BigBlueButtonWithCurlTransportTest.php index 9bfc9a0f..61d40766 100644 --- a/tests/functional/BigBlueButtonWithCurlTransportTest.php +++ b/tests/functional/BigBlueButtonWithCurlTransportTest.php @@ -26,9 +26,6 @@ final class BigBlueButtonWithCurlTransportTest extends AbstractBigBlueButtonFunctionalTest { - /** - * {@inheritDoc} - */ protected static function createTransport(): TransportInterface { return CurlTransport::createWithDefaultOptions(); diff --git a/tests/functional/BigBlueButtonWithPsrHttpClientTransport.php b/tests/functional/BigBlueButtonWithPsrHttpClientTransport.php index 3bebccef..cd5bd19c 100644 --- a/tests/functional/BigBlueButtonWithPsrHttpClientTransport.php +++ b/tests/functional/BigBlueButtonWithPsrHttpClientTransport.php @@ -29,9 +29,6 @@ final class BigBlueButtonWithPsrHttpClientTransport extends AbstractBigBlueButtonFunctionalTest { - /** - * {@inheritDoc} - */ protected static function createTransport(): TransportInterface { $psr17Factory = new Psr17Factory(); diff --git a/tests/functional/BigBlueButtonWithSymfonyHttpClientTransportTest.php b/tests/functional/BigBlueButtonWithSymfonyHttpClientTransportTest.php index 0dc733b8..3f75ce6f 100644 --- a/tests/functional/BigBlueButtonWithSymfonyHttpClientTransportTest.php +++ b/tests/functional/BigBlueButtonWithSymfonyHttpClientTransportTest.php @@ -26,9 +26,6 @@ final class BigBlueButtonWithSymfonyHttpClientTransportTest extends AbstractBigBlueButtonFunctionalTest { - /** - * {@inheritDoc} - */ protected static function createTransport(): TransportInterface { return SymfonyHttpClientTransport::create(); diff --git a/tests/integration/Http/Transport/CurlTransportTest.php b/tests/integration/Http/Transport/CurlTransportTest.php index 8517e011..0980173b 100644 --- a/tests/integration/Http/Transport/CurlTransportTest.php +++ b/tests/integration/Http/Transport/CurlTransportTest.php @@ -19,9 +19,11 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Http\Transport; +namespace BigBlueButton\Tests\Integration\Http\Transport; use BigBlueButton\Exceptions\NetworkException; +use BigBlueButton\Http\Transport\CurlTransport; +use BigBlueButton\Http\Transport\TransportRequest; use PHPUnit\Framework\TestCase; /** @@ -34,20 +36,18 @@ */ final class CurlTransportTest extends TestCase { - /** - * {@inheritDoc} - */ public static function setUpBeforeClass(): void { TestHttpServer::start(); } + /** @return array> */ public function provideBadResponseCodes(): iterable { // cURL does not understand codes below 200 properly. -// foreach (range(100, 199) as $badCode) { -// yield 'HTTP code ' . $badCode => [$badCode]; -// } + // foreach (range(100, 199) as $badCode) { + // yield 'HTTP code ' . $badCode => [$badCode]; + // } foreach (range(300, 599) as $badCode) { yield 'HTTP code '.$badCode => [$badCode]; @@ -80,6 +80,13 @@ public function testRequestWithPayloadAndAdditionalHeader(): void // BEWARE: Never do this in any production code. You have been warned. eval('$dump = '.$response->getBody().';'); + $this->assertArrayHasKey('input', $dump); + $this->assertArrayHasKey('vars', $dump); + $this->assertArrayHasKey('HTTP_CONTENT_LENGTH', $dump['vars']); + $this->assertArrayHasKey('HTTP_X_FOO', $dump['vars']); + $this->assertArrayHasKey('HTTP_X_BAR', $dump['vars']); + $this->assertArrayHasKey('REQUEST_METHOD', $dump['vars']); + $this->assertSame('FOO', $dump['input'], 'input echo is correct'); $this->assertSame('3', $dump['vars']['HTTP_CONTENT_LENGTH'], 'Content-Length echo is correct'); $this->assertSame('application/xml', $dump['vars']['HTTP_CONTENT_TYPE'], 'Content-Type echo is correct'); @@ -99,6 +106,11 @@ public function testRequestWithPayload(): void // BEWARE: Never do this in any production code. You have been warned. eval('$dump = '.$response->getBody().';'); + $this->assertArrayHasKey('input', $dump); + $this->assertArrayHasKey('vars', $dump); + $this->assertArrayHasKey('HTTP_CONTENT_LENGTH', $dump['vars']); + $this->assertArrayHasKey('REQUEST_METHOD', $dump['vars']); + $this->assertSame('FOO', $dump['input'], 'input echo is correct'); $this->assertSame('3', $dump['vars']['HTTP_CONTENT_LENGTH'], 'Content-Length echo is correct'); $this->assertSame('application/xml', $dump['vars']['HTTP_CONTENT_TYPE'], 'Content-Type echo is correct'); @@ -116,6 +128,10 @@ public function testRequestWithoutPayload(): void // BEWARE: Never do this in any production code. You have been warned. eval('$dump = '.$response->getBody().';'); + $this->assertArrayHasKey('input', $dump); + $this->assertArrayHasKey('vars', $dump); + $this->assertArrayHasKey('REQUEST_METHOD', $dump['vars']); + $this->assertSame('', $dump['input'], 'input echo is correct'); $this->assertSame('GET', $dump['vars']['REQUEST_METHOD'], 'request method echo is correct'); } @@ -153,6 +169,10 @@ public function testRequestWithDuplicatedHeader(): void // BEWARE: Never do this in any production code. You have been warned. eval('$dump = '.$response->getBody().';'); + $this->assertArrayHasKey('input', $dump); + $this->assertArrayHasKey('vars', $dump); + $this->assertArrayHasKey('HTTP_CONTENT_LENGTH', $dump['vars']); + $this->assertSame('FOO', $dump['input'], 'input echo is correct'); $this->assertSame('3', $dump['vars']['HTTP_CONTENT_LENGTH'], 'Content-Length echo is correct'); } diff --git a/tests/integration/Http/Transport/Fixtures/web/cookie.php b/tests/integration/Http/Transport/Fixtures/web/cookie.php index 51858fc3..d808ab0b 100644 --- a/tests/integration/Http/Transport/Fixtures/web/cookie.php +++ b/tests/integration/Http/Transport/Fixtures/web/cookie.php @@ -20,7 +20,7 @@ */ if ('cli-server' !== \PHP_SAPI) { // safe guard against unwanted execution - throw new \Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); + throw new Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); } setcookie('JSESSIONID', 'Monkey'); diff --git a/tests/integration/Http/Transport/Fixtures/web/double-newline.php b/tests/integration/Http/Transport/Fixtures/web/double-newline.php index 2a1e4999..bbc5f7fa 100644 --- a/tests/integration/Http/Transport/Fixtures/web/double-newline.php +++ b/tests/integration/Http/Transport/Fixtures/web/double-newline.php @@ -20,7 +20,7 @@ */ if ('cli-server' !== \PHP_SAPI) { // safe guard against unwanted execution - throw new \Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); + throw new Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); } echo "Foo\r\n\r\n"; diff --git a/tests/integration/Http/Transport/Fixtures/web/dump.php b/tests/integration/Http/Transport/Fixtures/web/dump.php index a74efeaa..31445fdc 100644 --- a/tests/integration/Http/Transport/Fixtures/web/dump.php +++ b/tests/integration/Http/Transport/Fixtures/web/dump.php @@ -29,7 +29,7 @@ if ('cli-server' !== \PHP_SAPI) { // safe guard against unwanted execution - throw new \Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); + throw new Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); } $vars = []; @@ -38,7 +38,7 @@ foreach ($_SERVER as $k => $v) { switch ($k) { default: - if (0 !== strpos($k, 'HTTP_')) { + if (!str_starts_with($k, 'HTTP_')) { continue 2; } // no break diff --git a/tests/integration/Http/Transport/Fixtures/web/no-cookie.php b/tests/integration/Http/Transport/Fixtures/web/no-cookie.php index 553afb62..6dae060f 100644 --- a/tests/integration/Http/Transport/Fixtures/web/no-cookie.php +++ b/tests/integration/Http/Transport/Fixtures/web/no-cookie.php @@ -20,7 +20,7 @@ */ if ('cli-server' !== \PHP_SAPI) { // safe guard against unwanted execution - throw new \Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); + throw new Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); } echo 'Hello from the other side!'; diff --git a/tests/integration/Http/Transport/Fixtures/web/response-code.php b/tests/integration/Http/Transport/Fixtures/web/response-code.php index e09e412d..bfc48b46 100644 --- a/tests/integration/Http/Transport/Fixtures/web/response-code.php +++ b/tests/integration/Http/Transport/Fixtures/web/response-code.php @@ -20,7 +20,7 @@ */ if ('cli-server' !== \PHP_SAPI) { // safe guard against unwanted execution - throw new \Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); + throw new Exception("You cannot run this script directly, it's a fixture for TestHttpServer."); } http_response_code((int) ($_GET['code'] ?? 200)); diff --git a/tests/integration/Http/Transport/TestHttpServer.php b/tests/integration/Http/Transport/TestHttpServer.php index 35192f2d..f5895d21 100644 --- a/tests/integration/Http/Transport/TestHttpServer.php +++ b/tests/integration/Http/Transport/TestHttpServer.php @@ -26,7 +26,7 @@ * THE SOFTWARE. */ -namespace BigBlueButton\Http\Transport; +namespace BigBlueButton\Tests\Integration\Http\Transport; use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; @@ -38,9 +38,10 @@ */ final class TestHttpServer { - private static $process = []; + /** @var array */ + private static array $process = []; - public static function start(int $port = 8057) + public static function start(int $port = 8057): Process { if (isset(self::$process[$port])) { self::$process[$port]->stop(); diff --git a/tests/unit/BigBlueButtonTest.php b/tests/unit/BigBlueButtonTest.php index 045cb7fb..944639c9 100644 --- a/tests/unit/BigBlueButtonTest.php +++ b/tests/unit/BigBlueButtonTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton; +namespace BigBlueButton\Tests\Unit; -use BigBlueButton\Core\ApiMethod; +use BigBlueButton\BigBlueButton; +use BigBlueButton\Enum\ApiMethod; +use BigBlueButton\Enum\HashingAlgorithm; use BigBlueButton\Exceptions\ConfigException; use BigBlueButton\Exceptions\NetworkException; use BigBlueButton\Exceptions\ParsingException; use BigBlueButton\Http\Transport\TransportInterface; use BigBlueButton\Http\Transport\TransportResponse; use BigBlueButton\Parameters\DeleteRecordingsParameters; +use BigBlueButton\Parameters\GetMeetingInfoParameters; use BigBlueButton\Parameters\GetRecordingsParameters; +use BigBlueButton\Parameters\GetRecordingTextTracksParameters; +use BigBlueButton\Parameters\HooksCreateParameters; +use BigBlueButton\Parameters\HooksDestroyParameters; +use BigBlueButton\Parameters\HooksListParameters; use BigBlueButton\Parameters\InsertDocumentParameters; use BigBlueButton\Parameters\PublishRecordingsParameters; +use BigBlueButton\Parameters\PutRecordingTextTrackParameters; +use BigBlueButton\Tests\Common\TestCase; use PHPUnit\Framework\MockObject\MockObject; /** * Class BigBlueButtonTest. */ -class BigBlueButtonTest extends TestCase +final class BigBlueButtonTest extends TestCase { - /** @var MockObject */ - private $transport; - - /** - * @var BigBlueButton - */ - private $bbb; + private MockObject $transport; + private BigBlueButton $bbb; /** * Setup test class. @@ -55,7 +62,7 @@ protected function setUp(): void $this->bbb = new BigBlueButton('http://localhost/', null, $this->transport); } - public function testMissingUrl() + public function testMissingUrl(): void { $this->expectException(ConfigException::class); @@ -69,7 +76,7 @@ public function testMissingUrl() } } - public function testNetworkFailure() + public function testNetworkFailure(): void { $this->expectException(NetworkException::class); @@ -80,7 +87,7 @@ public function testNetworkFailure() $this->bbb->createMeeting($this->getCreateMock($params)); } - public function testInvalidXMLResponse() + public function testInvalidXMLResponse(): void { $this->expectException(ParsingException::class); @@ -91,7 +98,7 @@ public function testInvalidXMLResponse() $this->bbb->createMeeting($this->getCreateMock($params)); } - public function testJSessionId() + public function testJSessionId(): void { $id = 'foobar'; $this->transport->method('request')->willReturn(new TransportResponse('', $id)); @@ -103,7 +110,7 @@ public function testJSessionId() $this->assertEquals($id, $this->bbb->getJSessionId()); } - public function testApiVersion() + public function testApiVersion(): void { $apiVersion = '2.0'; $xml = " @@ -119,7 +126,7 @@ public function testApiVersion() $this->assertEquals($apiVersion, $response->getVersion()); } - public function testIsConnectionWorking() + public function testIsConnectionWorking(): void { $xmlSuccess = ' SUCCESS @@ -152,21 +159,156 @@ public function testIsConnectionWorking() $this->assertEquals(BigBlueButton::CONNECTION_ERROR_BASEURL, $this->bbb->getConnectionError()); } + /* Get meeting info */ + + public function testGetMeetingInfoUrl(): void + { + $meetingId = $this->faker->uuid; + + $url = $this->bbb->getMeetingInfoUrl(new GetMeetingInfoParameters($meetingId)); + $this->assertStringContainsString('='.rawurlencode((string) $meetingId), $url); + } + + public function testGetMeetingInfo(): void + { + $meetingId = $this->faker->uuid; + + $xml = ' + SUCCESS + Demo Meeting + '.$meetingId.' + 79fd9d83ffdfe7d1fcfb19feab08d283ad518e49-1710416871174 + 1710416871174 + Thu Mar 14 11:47:51 UTC 2024 + 180621383 + 18632080022 + ap + mp + false + 540 + false + false + false + 1710416871176 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + false + '; + + $this->transport->method('request')->willReturn(new TransportResponse($xml, null)); + + $result = $this->bbb->getMeetingInfo(new GetMeetingInfoParameters($meetingId)); + $this->assertEquals('SUCCESS', $result->getReturnCode()); + $this->assertTrue($result->success()); + + $meeting = $result->getMeeting(); + + $this->assertEquals($meetingId, $meeting->getMeetingId()); + $this->assertEquals('Demo Meeting', $meeting->getMeetingName()); + } + /* Create Meeting */ /** * Test create meeting URL. */ - public function testCreateMeetingUrl() + public function testCreateMeetingUrl(): void { $params = $this->generateCreateParams(); $url = $this->bbb->getCreateMeetingUrl($this->getCreateMock($params)); - foreach ($params as $key => $value) { - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } - $this->assertStringContainsString(rawurlencode($key).'='.rawurlencode($value), $url); - } + + $this->assertUrlContainsAllRequestParameters($url, $params); + } + + /** + * Test create meeting without modules. + */ + public function testCreate(): void + { + $createMeetingParams = $this->generateCreateParams(); + $params = $this->getCreateMock($createMeetingParams); + + $xml = ' + SUCCESS + '.$params->getMeetingID().' + 1a6938c707cdf5d052958672d66c219c30690c47-1524212045514 + 1453283819419 + '.$params->getVoiceBridge().' + 613-555-1234 + Wed Jan 20 04:56:59 EST 2016 + false + 20 + false + '; + + $this->transport->method('request') + ->with(self::callback(static function ($request) { + $payload = $request->getPayload(); + + return $payload === ''; + })) + ->willReturn(new TransportResponse($xml, null)); + + $response = $this->bbb->createMeeting($params); + + $this->assertTrue($response->success()); + $this->assertFalse($response->isDuplicate()); + $this->assertFalse($response->isIdNotUnique()); + } + + /** + * Test create meeting with modules (presentations and clientSettingsOverride). + */ + public function testCreateWithPresentation(): void + { + $createMeetingParams = $this->generateCreateParams(); + $params = $this->getCreateMock($createMeetingParams); + $params->addPresentation('http://test-install.blindsidenetworks.com/default.pdf', null, 'presentation.pdf'); + $params->addPresentation('http://test-install.blindsidenetworks.com/file.pdf'); + $params->setClientSettingsOverride('{ "public": { "app": { "appName": "Test" } } }'); + + $xml = ' + SUCCESS + '.$params->getMeetingID().' + 1a6938c707cdf5d052958672d66c219c30690c47-1524212045514 + 1453283819419 + '.$params->getVoiceBridge().' + 613-555-1234 + Wed Jan 20 04:56:59 EST 2016 + false + 20 + false + '; + + $this->transport->method('request') + ->with(self::callback(static function ($request) { + $payload = $request->getPayload(); + $xml = simplexml_load_string($payload); + + $presentations = $xml->module[0]; + $clientSettingsOverride = $xml->module[1]; + + return \count($xml->module) == 2 + && $presentations->attributes()['name']->__toString() == 'presentation' + && $clientSettingsOverride->attributes()['name']->__toString() == 'clientSettingsOverride' + && $presentations->children()[0]->attributes()['url']->__toString() == 'http://test-install.blindsidenetworks.com/default.pdf' + && $presentations->children()[0]->attributes()['filename']->__toString() == 'presentation.pdf' + && $presentations->children()[1]->attributes()['url']->__toString() == 'http://test-install.blindsidenetworks.com/file.pdf'; + })) + ->willReturn(new TransportResponse($xml, null)); + + $response = $this->bbb->createMeeting($params); + + $this->assertTrue($response->success()); + $this->assertFalse($response->isDuplicate()); + $this->assertFalse($response->isIdNotUnique()); } /* Join Meeting */ @@ -174,22 +316,17 @@ public function testCreateMeetingUrl() /** * Test create join meeting URL. */ - public function testCreateJoinMeetingUrl() + public function testCreateJoinMeetingUrl(): void { $joinMeetingParams = $this->generateJoinMeetingParams(); $joinMeetingMock = $this->getJoinMeetingMock($joinMeetingParams); $url = $this->bbb->getJoinMeetingURL($joinMeetingMock); - foreach ($joinMeetingParams as $key => $value) { - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } - $this->assertStringContainsString(rawurlencode($key).'='.rawurlencode($value), $url); - } + $this->assertUrlContainsAllRequestParameters($url, $joinMeetingParams); } - public function testJoinMeeting() + public function testJoinMeeting(): void { $joinMeetingParams = $this->generateJoinMeetingParams(); $params = $this->getJoinMeetingMock($joinMeetingParams); @@ -225,19 +362,15 @@ public function testJoinMeeting() /** * Test generate end meeting URL. */ - public function testCreateEndMeetingUrl() + public function testCreateEndMeetingUrl(): void { $params = $this->generateEndMeetingParams(); $url = $this->bbb->getEndMeetingURL($this->getEndMeetingMock($params)); - foreach ($params as $key => $value) { - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } - $this->assertStringContainsString(rawurlencode($key).'='.rawurlencode($value), $url); - } + + $this->assertUrlContainsAllRequestParameters($url, $params); } - public function testEndMeeting() + public function testEndMeeting(): void { $data = $this->generateEndMeetingParams(); $params = $this->getEndMeetingMock($data); @@ -257,13 +390,13 @@ public function testEndMeeting() /* Get Meetings */ - public function testGetMeetingsUrl() + public function testGetMeetingsUrl(): void { $url = $this->bbb->getMeetingsUrl(); - $this->assertStringContainsString(ApiMethod::GET_MEETINGS, $url); + $this->assertStringContainsString(ApiMethod::GET_MEETINGS->value, $url); } - public function testGetMeetings() + public function testGetMeetings(): void { $xml = ' SUCCESS @@ -308,56 +441,158 @@ public function testGetMeetings() $this->assertEquals('12345', $response->getMeetings()[0]->getInternalMeetingId()); $this->assertEquals(1531241258036, $response->getMeetings()[0]->getCreationTime()); $this->assertEquals('Tue Jul 10 16:47:38 UTC 2018', $response->getMeetings()[0]->getCreationDate()); - $this->assertEquals('70066', $response->getMeetings()[0]->getVoiceBridge()); + $this->assertEquals(70066, $response->getMeetings()[0]->getVoiceBridge()); $this->assertEquals('613-555-1234', $response->getMeetings()[0]->getDialNumber()); - $this->assertEquals('ap', $response->getMeetings()[0]->getAttendeePassword()); - $this->assertEquals('mp', $response->getMeetings()[0]->getModeratorPassword()); $this->assertFalse($response->getMeetings()[0]->isRunning()); $this->assertEquals(0, $response->getMeetings()[0]->getDuration()); $this->assertFalse($response->getMeetings()[0]->hasUserJoined()); } - /* Get meeting info */ + /* Get recordings */ - public function testGetRecordingsUrl() + public function testGetRecordingsUrl(): void { $url = $this->bbb->getRecordingsUrl(new GetRecordingsParameters()); - $this->assertStringContainsString(ApiMethod::GET_RECORDINGS, $url); + $this->assertStringContainsString(ApiMethod::GET_RECORDINGS->value, $url); } - public function testPublishRecordingsUrl() + public function testGetRecordings(): void + { + $xml = ' + SUCCESS + + + f71d810b6e90a4a34ae02b8c7143e8733178578e-1462807897120 + 9d287cf50490ca856ca5273bd303a7e321df6051-4-119 + c654308ef4b71eeb74eb8436dc52a31415d9a911-1724671588959 + + true + published + 1462807897120 + 1462812873004 + + + + + + + + + + + + + + presentation + http://test-install.blindsidenetworks.com/playback/presentation/0.9.0/playback.html?meetingId=f71d810b6e90a4a34ae02b8c7143e8733178578e-1462807897120 + 44 + + + + + '; + + $this->transport->method('request')->willReturn(new TransportResponse($xml, null)); + + $response = $this->bbb->getRecordings(new GetRecordingsParameters()); + + $this->assertCount(1, $response->getRecords()); + $recording = $response->getRecords()[0]; + $this->assertEquals('f71d810b6e90a4a34ae02b8c7143e8733178578e-1462807897120', $recording->getRecordID()); + $this->assertEquals('9d287cf50490ca856ca5273bd303a7e321df6051-4-119', $recording->getMeetingID()); + $this->assertEquals('c654308ef4b71eeb74eb8436dc52a31415d9a911-1724671588959', $recording->getInternalMeetingID()); + $this->assertEquals('SAT- Writing-Humanities (All participants)', $recording->getName()); + } + + /* Publish recordings */ + + public function testPublishRecordingsUrl(): void { $url = $this->bbb->getPublishRecordingsUrl(new PublishRecordingsParameters($this->faker->sha1, true)); - $this->assertStringContainsString(ApiMethod::PUBLISH_RECORDINGS, $url); + $this->assertStringContainsString(ApiMethod::PUBLISH_RECORDINGS->value, $url); + } + + public function testPublishRecordings(): void + { + $xml = ' + SUCCESS + true + '; + + $this->transport->method('request')->willReturn(new TransportResponse($xml, null)); + + $result = $this->bbb->publishRecordings(new PublishRecordingsParameters($this->faker->sha1, true)); + + $this->assertTrue($result->isPublished()); } - public function testDeleteRecordingsUrl() + /* Delete recordings */ + + public function testDeleteRecordingsUrl(): void { $url = $this->bbb->getDeleteRecordingsUrl(new DeleteRecordingsParameters($this->faker->sha1)); - $this->assertStringContainsString(ApiMethod::DELETE_RECORDINGS, $url); + $this->assertStringContainsString(ApiMethod::DELETE_RECORDINGS->value, $url); } - public function testUpdateRecordingsUrl() + public function testDeleteRecordings(): void + { + $xml = ' + SUCCESS + true + '; + + $this->transport->method('request')->willReturn(new TransportResponse($xml, null)); + + $result = $this->bbb->deleteRecordings(new DeleteRecordingsParameters($this->faker->sha1)); + + $this->assertTrue($result->isDeleted()); + } + + /* Update recordings */ + + public function testUpdateRecordingsUrl(): void { $params = $this->generateUpdateRecordingsParams(); $url = $this->bbb->getUpdateRecordingsUrl($this->getUpdateRecordingsParamsMock($params)); - foreach ($params as $key => $value) { - if (\is_bool($value)) { - $value = $value ? 'true' : 'false'; - } - $this->assertStringContainsString(rawurlencode($key).'='.rawurlencode($value), $url); - } + + $this->assertUrlContainsAllRequestParameters($url, $params); + } + + public function testUpdateRecordings(): void + { + $params = $this->generateUpdateRecordingsParams(); + + $xml = ' + SUCCESS + true + '; + + $this->transport->method('request')->willReturn(new TransportResponse($xml, null)); + + $result = $this->bbb->updateRecordings($this->getUpdateRecordingsParamsMock($params)); + + $this->assertTrue($result->isUpdated()); } public function testBuildUrl(): void { - $bigBlueButton = new BigBlueButton('https://bbb.example/bigbluebutton/', 'S3cr3t'); + // Test with default hash algorithm (sha1) + $bigBlueButton = new BigBlueButton('https://bbb.example/bigbluebutton/', 'S3cr3t', null, HashingAlgorithm::SHA_1); $this->assertSame( 'https://bbb.example/bigbluebutton/api/foo?foo=bar&baz=bazinga&checksum=694ad46bc5a79a572bab6c8b9a939527c39ac7f6', $bigBlueButton->buildUrl('foo', 'foo=bar&baz=bazinga'), 'URL is not ok' ); + + // Test with different hash algorithm (sha256) + $bigBlueButton = new BigBlueButton('https://bbb.example/bigbluebutton/', 'S3cr3t', null, HashingAlgorithm::SHA_256); + + $this->assertSame( + 'https://bbb.example/bigbluebutton/api/foo?foo=bar&baz=bazinga&checksum=0ce0d779a8220be9824c7eab055b36b59ac504ba899a76d7c528b8473960025e', + $bigBlueButton->buildUrl('foo', 'foo=bar&baz=bazinga'), + 'URL is not ok' + ); } public function testGetInsertDocument(): void @@ -372,4 +607,178 @@ public function testGetInsertDocument(): void $this->assertTrue($response->success()); } + + public function testGetRecordingTextTracks(): void + { + $params = new GetRecordingTextTracksParameters('foobar'); + + $json = '{ + "response": { + "returncode": "SUCCESS", + "tracks": [ + { + "href": "https://captions.example.com/textTrack/0ab39e419c9bcb63233168daefe390f232c71343/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1554230749920/subtitles_en-US.vtt", + "kind": "subtitles", + "label": "English", + "lang": "en-US", + "source": "upload" + }, + { + "href": "https://captions.example.com/textTrack/95b62d1b762700b9d5366a9e71d5fcc5086f2723/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1554230749920/subtitles_pt-BR.vtt", + "kind": "subtitles", + "label": "Brazil", + "lang": "pt-BR", + "source": "upload" + } + ] + } + }'; + $this->transport->method('request')->willReturn(new TransportResponse($json, null)); + + $response = $this->bbb->getRecordingTextTracks($params); + + $this->assertTrue($response->success()); + $this->assertSame('SUCCESS', $response->getReturnCode()); + + $tracks = $response->getTracks(); + $this->assertCount(2, $tracks); + $this->assertArrayHasKey(0, $tracks); + $this->assertArrayHasKey(1, $tracks); + + $this->assertSame('https://captions.example.com/textTrack/0ab39e419c9bcb63233168daefe390f232c71343/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1554230749920/subtitles_en-US.vtt', $tracks[0]->getHref()); + $this->assertSame('subtitles', $tracks[0]->getKind()); + $this->assertSame('English', $tracks[0]->getLabel()); + $this->assertSame('en-US', $tracks[0]->getLang()); + $this->assertSame('upload', $tracks[0]->getSource()); + + $this->assertSame('https://captions.example.com/textTrack/95b62d1b762700b9d5366a9e71d5fcc5086f2723/183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1554230749920/subtitles_pt-BR.vtt', $tracks[1]->getHref()); + $this->assertSame('subtitles', $tracks[1]->getKind()); + $this->assertSame('Brazil', $tracks[1]->getLabel()); + $this->assertSame('pt-BR', $tracks[1]->getLang()); + $this->assertSame('upload', $tracks[1]->getSource()); + } + + public function testPutRecordingTextTrack(): void + { + $params = new PutRecordingTextTrackParameters('foobar', 'subtitles', 'en-US', 'English'); + + $json = '{ + "response": { + "messageKey": "upload_text_track_success", + "message": "Text track uploaded successfully", + "recordId": "baz", + "returncode": "SUCCESS" + } + }'; + $this->transport->method('request')->willReturn(new TransportResponse($json, null)); + + $response = $this->bbb->putRecordingTextTrack($params); + + $this->assertTrue($response->success()); + $this->assertEquals('upload_text_track_success', $response->getMessageKey()); + $this->assertEquals('Text track uploaded successfully', $response->getMessage()); + $this->assertSame('baz', $response->getRecordID()); + $this->assertSame('SUCCESS', $response->getReturnCode()); + } + + public function testHooksCreate(): void + { + $params = new HooksCreateParameters($this->faker->url); + + $xml = ' + SUCCESS + 1 + false + false + '; + + $this->transport->method('request')->willReturn(new TransportResponse($xml, null)); + + $response = $this->bbb->hooksCreate($params); + + $this->assertTrue($response->success()); + $this->assertSame(1, $response->getHookId()); + $this->assertFalse($response->isPermanentHook()); + $this->assertFalse($response->hasRawData()); + } + + public function testHooksList(): void + { + $params = new HooksListParameters(); + + $xml = ' + SUCCESS + + + 1 + + + false + false + + + 2 + + false + false + + + '; + + $this->transport->method('request')->willReturn(new TransportResponse($xml, null)); + + $response = $this->bbb->hooksList($params); + + $this->assertTrue($response->success()); + $this->assertCount(2, $response->getHooks()); + + // Hook for a single meeting + $meetingHook = $response->getHooks()[0]; + $this->assertSame(1, $meetingHook->getHookId()); + $this->assertSame('http://postcatcher.in/catchers/abcdefghijk', $meetingHook->getCallbackURL()); + $this->assertSame('my-meeting', $meetingHook->getMeetingID()); + $this->assertFalse($meetingHook->isPermanentHook()); + $this->assertFalse($meetingHook->hasRawData()); + + // Global hook + $globalHook = $response->getHooks()[1]; + $this->assertSame(2, $globalHook->getHookId()); + $this->assertSame('http://postcatcher.in/catchers/1234567890', $globalHook->getCallbackURL()); + $this->assertFalse($globalHook->isPermanentHook()); + $this->assertFalse($globalHook->hasRawData()); + } + + public function testHooksListUrl(): void + { + // Test without meeting ID + $params = new HooksListParameters(); + $url = $this->bbb->getHooksListUrl($params); + + $this->assertStringContainsString(ApiMethod::HOOKS_LIST->value, $url); + $this->assertStringNotContainsString('meetingID=', $url); + + // Test with meeting ID + $params = new HooksListParameters(); + $params->setMeetingID('foobar'); + $url = $this->bbb->getHooksListUrl($params); + + $this->assertStringContainsString('meetingID=foobar', $url); + } + + public function testHookDestroy(): void + { + $params = new HooksDestroyParameters('1'); + + $xml = ' + SUCCESS + true + '; + + $this->transport->method('request')->willReturn(new TransportResponse($xml, null)); + + $response = $this->bbb->hooksDestroy($params); + + $this->assertTrue($response->success()); + $this->assertTrue($response->removed()); + } } diff --git a/tests/unit/Http/SetCookieTest.php b/tests/unit/Http/SetCookieTest.php index 588ae033..8ad3a0c8 100644 --- a/tests/unit/Http/SetCookieTest.php +++ b/tests/unit/Http/SetCookieTest.php @@ -24,8 +24,9 @@ * THE SOFTWARE. */ -namespace BigBlueButton\Http; +namespace BigBlueButton\Tests\Unit\Http; +use BigBlueButton\Http\SetCookie; use PHPUnit\Framework\TestCase; /** @@ -148,6 +149,7 @@ public function testMatchesDomain(): void self::assertFalse($cookie->matchesDomain('example.com')); } + /** @return array> */ public function pathMatchProvider(): array { return [ @@ -179,6 +181,7 @@ public function testMatchesPath(string $cookiePath, string $requestPath, bool $i self::assertSame($isMatch, $cookie->matchesPath($requestPath)); } + /** @return array> */ public function cookieValidateProvider(): array { return [ @@ -234,6 +237,8 @@ public function testConvertsToString(): void /** * Provides the parsed information from a cookie. + * + * @return array */ public function cookieParserDataProvider(): array { @@ -392,9 +397,10 @@ public function cookieParserDataProvider(): array /** * @dataProvider cookieParserDataProvider * - * @param string|array $cookie + * @param array|string $cookie + * @param array $parsed */ - public function testParseCookie($cookie, array $parsed): void + public function testParseCookie(array|string $cookie, array $parsed): void { foreach ((array) $cookie as $v) { $c = SetCookie::fromString($v); @@ -402,7 +408,7 @@ public function testParseCookie($cookie, array $parsed): void if (isset($p['Expires'])) { $delta = 40; - $parsedExpires = is_numeric($parsed['Expires']) ? $parsed['Expires'] : strtotime($parsed['Expires']); + $parsedExpires = is_numeric($parsed['Expires']) ? $parsed['Expires'] : strtotime((string) $parsed['Expires']); self::assertLessThan($delta, abs($p['Expires'] - $parsedExpires), 'Comparing Expires '.var_export($p['Expires'], true).' : '.var_export($parsed, true).' | '.var_export($p, true)); unset($p['Expires']); unset($parsed['Expires']); @@ -433,6 +439,8 @@ public function testParseCookie($cookie, array $parsed): void /** * Provides the data for testing isExpired. + * + * @return array> */ public function isExpiredProvider(): array { diff --git a/tests/unit/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransportTest.php b/tests/unit/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransportTest.php index 73d97637..093d23cb 100644 --- a/tests/unit/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransportTest.php +++ b/tests/unit/Http/Transport/Bridge/PsrHttpClient/PsrHttpClientTransportTest.php @@ -19,10 +19,11 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Http\Transport\Bridge\PsrHttpClient; +namespace BigBlueButton\Tests\Unit\Http\Transport\Bridge\PsrHttpClient; use BigBlueButton\Exceptions\NetworkException; use BigBlueButton\Exceptions\RuntimeException; +use BigBlueButton\Http\Transport\Bridge\PsrHttpClient\PsrHttpClientTransport; use BigBlueButton\Http\Transport\TransportRequest; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -43,28 +44,22 @@ */ final class PsrHttpClientTransportTest extends TestCase { - /** - * @var PsrHttpClientTransport - */ - private $transport; + private PsrHttpClientTransport $transport; /** * @var MockObject&ClientInterface */ - private $httpClientMock; + private ?MockObject $httpClientMock = null; /** * @var MockObject&RequestFactoryInterface */ - private $requestFactoryMock; + private ?MockObject $requestFactoryMock = null; /** * @var MockObject&StreamFactoryInterface */ - private $streamFactoryMock; + private ?MockObject $streamFactoryMock = null; - /** - * {@inheritDoc} - */ protected function setUp(): void { $this->transport = $this->createTransport(); @@ -203,6 +198,7 @@ public function testRequestWithClientException(): void $this->transport->request($request); } + /** @return iterable> */ public function provideBadResponseCodes(): iterable { foreach (range(100, 199) as $badCode) { diff --git a/tests/unit/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransportTest.php b/tests/unit/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransportTest.php index 34c1f9a0..740596ce 100644 --- a/tests/unit/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransportTest.php +++ b/tests/unit/Http/Transport/Bridge/SymfonyHttpClient/SymfonyHttpClientTransportTest.php @@ -19,10 +19,11 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Http\Transport\Bridge\SymfonyHttpClient; +namespace BigBlueButton\Tests\Unit\Http\Transport\Bridge\SymfonyHttpClient; use BigBlueButton\Exceptions\NetworkException; use BigBlueButton\Exceptions\RuntimeException; +use BigBlueButton\Http\Transport\Bridge\SymfonyHttpClient\SymfonyHttpClientTransport; use BigBlueButton\Http\Transport\TransportRequest; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpClient\Exception\TransportException; @@ -200,6 +201,7 @@ public function testRequestWithDefaultOptions(): void } } + /** @return iterable> */ public function provideBadResponseCodes(): iterable { foreach (range(100, 199) as $badCode) { @@ -241,6 +243,7 @@ public function testRequestWithBadResponseCode(int $badCode): void } } + /** @return iterable> */ public function provideBadResponseExceptions(): iterable { yield 'Client exception' => [new FakeClientException(), 400]; diff --git a/tests/unit/Http/Transport/CookieTest.php b/tests/unit/Http/Transport/CookieTest.php index aab7cd0e..5b131aa6 100644 --- a/tests/unit/Http/Transport/CookieTest.php +++ b/tests/unit/Http/Transport/CookieTest.php @@ -19,8 +19,9 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Http\Transport; +namespace BigBlueButton\Tests\Unit\Http\Transport; +use BigBlueButton\Http\Transport\Cookie; use PHPUnit\Framework\TestCase; /** diff --git a/tests/unit/Http/Transport/HeaderTest.php b/tests/unit/Http/Transport/HeaderTest.php index 70abb5d5..d823c559 100644 --- a/tests/unit/Http/Transport/HeaderTest.php +++ b/tests/unit/Http/Transport/HeaderTest.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace BigBlueButton\Http; +namespace BigBlueButton\Tests\Unit\Http; /** * This file is part of littleredbutton/bigbluebutton-api-php. @@ -21,7 +21,7 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Http; +namespace BigBlueButton\Tests\Unit\Http\Transport; use BigBlueButton\Http\Transport\Header; use PHPUnit\Framework\TestCase; @@ -61,7 +61,7 @@ public function provideBadlyFormattedHeaders(): iterable public function testMergeCurlHeadersWithBadHeaders(string $badHeader): void { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf('Header value "%s" is invalid. Expected format is "Header-Name: value".', $badHeader)); + $this->expectExceptionMessage(\sprintf('Header value "%s" is invalid. Expected format is "Header-Name: value".', $badHeader)); Header::mergeCurlHeaders([$badHeader]); } @@ -78,15 +78,13 @@ public function provideNonStringHeaders(): iterable /** * @dataProvider provideNonStringHeaders - * - * @param mixed $badHeader */ - public function testMergeCurlHeadersWithNonStringHeaders($badHeader): void + public function testMergeCurlHeadersWithNonStringHeaders(mixed $badHeader): void { $this->expectException(\InvalidArgumentException::class); - $this->expectExceptionMessage(sprintf( + $this->expectExceptionMessage(\sprintf( 'Non-string header with type "%s" passed.', - \is_object($badHeader) ? \get_class($badHeader) : \gettype($badHeader) + get_debug_type($badHeader) )); Header::mergeCurlHeaders([$badHeader]); diff --git a/tests/unit/Http/Transport/TransportRequestTest.php b/tests/unit/Http/Transport/TransportRequestTest.php index ec9a728f..4ec702a1 100644 --- a/tests/unit/Http/Transport/TransportRequestTest.php +++ b/tests/unit/Http/Transport/TransportRequestTest.php @@ -19,7 +19,7 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Http; +namespace BigBlueButton\Tests\Unit\Http; use BigBlueButton\Http\Transport\TransportRequest; use PHPUnit\Framework\TestCase; diff --git a/tests/unit/Http/Transport/TransportResponseTest.php b/tests/unit/Http/Transport/TransportResponseTest.php index 5ab01bdc..adf293bd 100644 --- a/tests/unit/Http/Transport/TransportResponseTest.php +++ b/tests/unit/Http/Transport/TransportResponseTest.php @@ -19,8 +19,9 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Http\Transport; +namespace BigBlueButton\Tests\Unit\Http\Transport; +use BigBlueButton\Http\Transport\TransportResponse; use PHPUnit\Framework\TestCase; /** diff --git a/tests/unit/Parameters/BaseParametersTest.php b/tests/unit/Parameters/BaseParametersTest.php index 9a430c7f..9645f10b 100644 --- a/tests/unit/Parameters/BaseParametersTest.php +++ b/tests/unit/Parameters/BaseParametersTest.php @@ -19,8 +19,9 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; +use BigBlueButton\Parameters\BaseParameters; use PHPUnit\Framework\TestCase; final class BaseParametersTest extends TestCase @@ -54,6 +55,17 @@ public function testSetterWithInvalid(): void $params->setInvalid('foobar'); } + + public function testEnum(): void + { + $params = new TestEnumParameters(); + + $params->setEnum('one'); + $this->assertSame(TestEnum::ONE, $params->getEnum()); + + $params->setEnum('two'); + $this->assertSame(TestEnum::TWO, $params->getEnum()); + } } /** @@ -65,5 +77,22 @@ public function testSetterWithInvalid(): void */ final class TestParameters extends BaseParameters { - protected $notABool = 'string'; + protected string $notABool = 'string'; +} + +/** + * @internal + * + * @method self setEnum(TestEnum|string $enum) + * @method TestEnum getEnum() + */ +final class TestEnumParameters extends BaseParameters +{ + protected ?TestEnum $enum = null; +} + +enum TestEnum: string +{ + case ONE = 'one'; + case TWO = 'two'; } diff --git a/tests/unit/Parameters/CreateMeetingParametersTest.php b/tests/unit/Parameters/CreateMeetingParametersTest.php index e951e777..be94bead 100644 --- a/tests/unit/Parameters/CreateMeetingParametersTest.php +++ b/tests/unit/Parameters/CreateMeetingParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\Core\GuestPolicy; -use BigBlueButton\TestCase; -use PHPUnit\Framework\Error\Warning; +use BigBlueButton\Enum\Feature; +use BigBlueButton\Enum\GuestPolicy; +use BigBlueButton\Parameters\CreateMeetingParameters; +use BigBlueButton\Tests\Common\TestCase; /** * Class CreateMeetingParametersTest. */ -class CreateMeetingParametersTest extends TestCase +final class CreateMeetingParametersTest extends TestCase { - public function testCreateMeetingParameters() + public function testCreateMeetingParameters(): void { $params = $this->generateCreateParams(); $createMeetingParams = $this->getCreateMock($params); $this->assertEquals($params['name'], $createMeetingParams->getName()); $this->assertEquals($params['meetingID'], $createMeetingParams->getMeetingID()); - $this->assertEquals($params['attendeePW'], $createMeetingParams->getAttendeePW()); - $this->assertEquals($params['moderatorPW'], $createMeetingParams->getModeratorPW()); $this->assertEquals($params['autoStartRecording'], $createMeetingParams->isAutoStartRecording()); $this->assertEquals($params['dialNumber'], $createMeetingParams->getDialNumber()); $this->assertEquals($params['voiceBridge'], $createMeetingParams->getVoiceBridge()); @@ -49,6 +51,7 @@ public function testCreateMeetingParameters() $this->assertEquals($params['moderatorOnlyMessage'], $createMeetingParams->getModeratorOnlyMessage()); $this->assertEquals($params['webcamsOnlyForModerator'], $createMeetingParams->isWebcamsOnlyForModerator()); $this->assertEquals($params['logo'], $createMeetingParams->getLogo()); + $this->assertEquals($params['darklogo'], $createMeetingParams->getDarklogo()); $this->assertEquals($params['copyright'], $createMeetingParams->getCopyright()); $this->assertEquals($params['muteOnStart'], $createMeetingParams->isMuteOnStart()); $this->assertEquals($params['guestPolicy'], $createMeetingParams->getGuestPolicy()); @@ -56,19 +59,18 @@ public function testCreateMeetingParameters() $this->assertEquals($params['lockSettingsDisableMic'], $createMeetingParams->isLockSettingsDisableMic()); $this->assertEquals($params['lockSettingsDisablePrivateChat'], $createMeetingParams->isLockSettingsDisablePrivateChat()); $this->assertEquals($params['lockSettingsDisablePublicChat'], $createMeetingParams->isLockSettingsDisablePublicChat()); - $this->assertEquals($params['lockSettingsDisableNote'], $createMeetingParams->isLockSettingsDisableNote()); + $this->assertEquals($params['lockSettingsDisableNotes'], $createMeetingParams->isLockSettingsDisableNotes()); $this->assertEquals($params['lockSettingsLockedLayout'], $createMeetingParams->isLockSettingsLockedLayout()); $this->assertEquals($params['lockSettingsHideUserList'], $createMeetingParams->isLockSettingsHideUserList()); $this->assertEquals($params['lockSettingsLockOnJoin'], $createMeetingParams->isLockSettingsLockOnJoin()); $this->assertEquals($params['lockSettingsLockOnJoinConfigurable'], $createMeetingParams->isLockSettingsLockOnJoinConfigurable()); $this->assertEquals($params['allowModsToUnmuteUsers'], $createMeetingParams->isAllowModsToUnmuteUsers()); $this->assertEquals($params['allowModsToEjectCameras'], $createMeetingParams->isAllowModsToEjectCameras()); + $this->assertEquals($params['allowPromoteGuestToModerator'], $createMeetingParams->isAllowPromoteGuestToModerator()); $this->assertEquals($params['guestPolicy'], $createMeetingParams->getGuestPolicy()); $this->assertEquals($params['endWhenNoModerator'], $createMeetingParams->isEndWhenNoModerator()); $this->assertEquals($params['endWhenNoModeratorDelayInMinutes'], $createMeetingParams->getEndWhenNoModeratorDelayInMinutes()); - $this->assertEquals($params['learningDashboardEnabled'], $createMeetingParams->isLearningDashboardEnabled()); $this->assertEquals($params['learningDashboardCleanupDelayInMinutes'], $createMeetingParams->getLearningDashboardCleanupDelayInMinutes()); - $this->assertEquals($params['breakoutRoomsEnabled'], $createMeetingParams->isBreakoutRoomsEnabled()); $this->assertEquals($params['breakoutRoomsRecord'], $createMeetingParams->isBreakoutRoomsRecord()); $this->assertEquals($params['breakoutRoomsPrivateChatEnabled'], $createMeetingParams->isBreakoutRoomsPrivateChatEnabled()); $this->assertEquals($params['meetingEndedURL'], $createMeetingParams->getMeetingEndedURL()); @@ -82,14 +84,14 @@ public function testCreateMeetingParameters() $this->assertEquals($params['endWhenNoModerator'], $createMeetingParams->isEndWhenNoModerator()); $this->assertEquals($params['endWhenNoModeratorDelayInMinutes'], $createMeetingParams->getEndWhenNoModeratorDelayInMinutes()); $this->assertEquals($params['meetingLayout'], $createMeetingParams->getMeetingLayout()); - $this->assertEquals($params['learningDashboardEnabled'], $createMeetingParams->isLearningDashboardEnabled()); $this->assertEquals($params['learningDashboardCleanupDelayInMinutes'], $createMeetingParams->getLearningDashboardCleanupDelayInMinutes()); $this->assertEquals($params['allowModsToEjectCameras'], $createMeetingParams->isAllowModsToEjectCameras()); - $this->assertEquals($params['breakoutRoomsEnabled'], $createMeetingParams->isBreakoutRoomsEnabled()); $this->assertEquals($params['breakoutRoomsPrivateChatEnabled'], $createMeetingParams->isBreakoutRoomsPrivateChatEnabled()); $this->assertEquals($params['breakoutRoomsRecord'], $createMeetingParams->isBreakoutRoomsRecord()); $this->assertEquals($params['allowRequestsWithoutSession'], $createMeetingParams->isAllowRequestsWithoutSession()); - $this->assertEquals($params['virtualBackgroundsDisabled'], $createMeetingParams->isVirtualBackgroundsDisabled()); + $this->assertEquals(json_encode($params['groups']), json_encode($createMeetingParams->getBreakoutRoomsGroups())); + $this->assertEquals($params['disabledFeatures'], $createMeetingParams->getDisabledFeatures()); + $this->assertEquals($params['disabledFeaturesExclude'], $createMeetingParams->getDisabledFeaturesExclude()); // Check values are empty of this is not a breakout room $this->assertNull($createMeetingParams->isBreakout()); @@ -104,7 +106,54 @@ public function testCreateMeetingParameters() $this->assertEquals($newId, $createMeetingParams->getMeetingID()); } - public function testCreateBreakoutMeeting() + public function testMetaParameters(): void + { + $params = $this->generateCreateParams(); + $createMeetingParams = $this->getCreateMock($params); + $createMeetingParams->addMeta('userdata-bbb_hide_presentation_on_join', true); + $createMeetingParams->addMeta('userdata-bbb_show_participants_on_login', false); + $createMeetingParams->addMeta('userdata-bbb_fullaudio_bridge', 'fullaudio'); + + // Test getters + $this->assertTrue($createMeetingParams->getMeta('userdata-bbb_hide_presentation_on_join')); + $this->assertFalse($createMeetingParams->getMeta('userdata-bbb_show_participants_on_login')); + $this->assertEquals('fullaudio', $createMeetingParams->getMeta('userdata-bbb_fullaudio_bridge')); + + $params = urldecode($createMeetingParams->getHTTPQuery()); + + // Test HTTP query + $this->assertStringContainsString('meta_userdata-bbb_hide_presentation_on_join=true', $params); + $this->assertStringContainsString('meta_userdata-bbb_show_participants_on_login=false', $params); + $this->assertStringContainsString('meta_userdata-bbb_fullaudio_bridge=fullaudio', $params); + } + + public function testDisabledFeatures(): void + { + $params = $this->generateCreateParams(); + $createMeetingParams = $this->getCreateMock($params); + + // Test empty disabled features + $createMeetingParams->setDisabledFeatures([]); + $params = urldecode($createMeetingParams->getHTTPQuery()); + $this->assertStringNotContainsString('disabledFeatures=', $params); + + // Test with multiple disabled features + $createMeetingParams->setDisabledFeatures([Feature::CHAT, Feature::POLLS, Feature::CAPTIONS]); + $params = urldecode($createMeetingParams->getHTTPQuery()); + $this->assertStringContainsString('disabledFeatures=chat,polls,captions', $params); + + // Test empty disabled features exclude + $createMeetingParams->setDisabledFeaturesExclude([]); + $params = urldecode($createMeetingParams->getHTTPQuery()); + $this->assertStringNotContainsString('disabledFeaturesExclude=', $params); + + // Test with multiple disabled features exclude + $createMeetingParams->setDisabledFeaturesExclude([Feature::CHAT, Feature::POLLS]); + $params = urldecode($createMeetingParams->getHTTPQuery()); + $this->assertStringContainsString('disabledFeaturesExclude=chat,polls', $params); + } + + public function testCreateBreakoutMeeting(): void { $params = $this->generateBreakoutCreateParams($this->generateCreateParams()); $createBreakoutMeetingParams = $this->getBreakoutCreateMock($params); @@ -117,57 +166,59 @@ public function testCreateBreakoutMeeting() $this->assertStringContainsString('isBreakout='.rawurlencode($createBreakoutMeetingParams->isBreakout() ? 'true' : 'false'), $params); $this->assertStringContainsString('parentMeetingID='.rawurlencode($createBreakoutMeetingParams->getParentMeetingID()), $params); - $this->assertStringContainsString('sequence='.rawurlencode($createBreakoutMeetingParams->getSequence()), $params); + $this->assertStringContainsString('sequence='.rawurlencode((string) $createBreakoutMeetingParams->getSequence()), $params); $this->assertStringContainsString('freeJoin='.rawurlencode($createBreakoutMeetingParams->isFreeJoin() ? 'true' : 'false'), $params); } - public function testCreateBreakoutMeetingWithMissingParams() + public function testCreateBreakoutMeetingWithMissingParams(): void { - $this->expectException(Warning::class); + $this->expectException(\RuntimeException::class); $params = new CreateMeetingParameters($this->faker->uuid, $this->faker->name); $params->setBreakout(true); $params->getHTTPQuery(); } - public function testNonExistingProperty() + public function testNonExistingProperty(): void { $this->expectException(\BadFunctionCallException::class); $params = new CreateMeetingParameters($this->faker->uuid, $this->faker->name); + /* @phpstan-ignore-next-line */ $params->getFoobar(); } - public function testWrongMethodName() + public function testWrongMethodName(): void { $this->expectException(\BadFunctionCallException::class); $params = new CreateMeetingParameters($this->faker->uuid, $this->faker->name); + /* @phpstan-ignore-next-line */ $params->getname(); } - public function testGetPresentationsAsXMLWithUrl() + public function testGetPresentationsAsXMLWithUrl(): void { $params = $this->generateCreateParams(); $createMeetingParams = $this->getCreateMock($params); $createMeetingParams->addPresentation('http://test-install.blindsidenetworks.com/default.pdf'); - $this->assertXmlStringEqualsXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'presentation_with_url.xml', $createMeetingParams->getPresentationsAsXML()); + $this->assertXmlStringEqualsXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'presentation_with_url.xml', $createMeetingParams->getModules()); } - public function testGetPresentationsAsXMLWithUrlAndFilename() + public function testGetPresentationsAsXMLWithUrlAndFilename(): void { $params = $this->generateCreateParams(); $createMeetingParams = $this->getCreateMock($params); $createMeetingParams->addPresentation('http://test-install.blindsidenetworks.com/default.pdf', null, 'presentation.pdf'); - $this->assertXmlStringEqualsXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'presentation_with_filename.xml', $createMeetingParams->getPresentationsAsXML()); + $this->assertXmlStringEqualsXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'presentation_with_filename.xml', $createMeetingParams->getModules()); } - public function testGetPresentationsAsXMLWithFile() + public function testGetPresentationsAsXMLWithFile(): void { $params = $this->generateCreateParams(); $createMeetingParams = $this->getCreateMock($params); $createMeetingParams->addPresentation('bbb_logo.png', file_get_contents(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'bbb_logo.png')); - $this->assertXmlStringEqualsXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'presentation_with_embedded_file.xml', $createMeetingParams->getPresentationsAsXML()); + $this->assertXmlStringEqualsXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'presentation_with_embedded_file.xml', $createMeetingParams->getModules()); } public function testUserCameraCap(): void @@ -221,4 +272,14 @@ public function testGuestPolicyAskModerator(): void $this->assertSame(GuestPolicy::ASK_MODERATOR, $createMeetingParams->getGuestPolicy()); $this->assertTrue($createMeetingParams->isGuestPolicyAskModerator()); } + + public function testClientSettingsOverride(): void + { + $params = $this->generateCreateParams(); + $createMeetingParams = $this->getCreateMock($params); + + $createMeetingParams->setClientSettingsOverride('{ "public": { "app": { "appName": "Test" } } }'); + + $this->assertXmlStringEqualsXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'client_settings.xml', $createMeetingParams->getModules()); + } } diff --git a/tests/unit/Parameters/DeleteRecordingsParametersTest.php b/tests/unit/Parameters/DeleteRecordingsParametersTest.php index b183b1af..1b7e0346 100644 --- a/tests/unit/Parameters/DeleteRecordingsParametersTest.php +++ b/tests/unit/Parameters/DeleteRecordingsParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\DeleteRecordingsParameters; +use BigBlueButton\Tests\Common\TestCase; -class DeleteRecordingsParametersTest extends TestCase +final class DeleteRecordingsParametersTest extends TestCase { - public function testDeleteRecordingParameter() + public function testDeleteRecordingParameter(): void { $recordingId = $this->faker->uuid; $deleteRecording = new DeleteRecordingsParameters($recordingId); diff --git a/tests/unit/Parameters/EndMeetingParametersTest.php b/tests/unit/Parameters/EndMeetingParametersTest.php index 2873bb0a..45a3693c 100644 --- a/tests/unit/Parameters/EndMeetingParametersTest.php +++ b/tests/unit/Parameters/EndMeetingParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\EndMeetingParameters; +use BigBlueButton\Tests\Common\TestCase; -class EndMeetingParametersTest extends TestCase +final class EndMeetingParametersTest extends TestCase { - public function testEndMeetingParameters() + public function testEndMeetingParameters(): void { - $endMeetingParams = new EndMeetingParameters($meetingId = $this->faker->uuid, $password = $this->faker->password()); + $endMeetingParams = new EndMeetingParameters($meetingId = $this->faker->uuid); $this->assertEquals($meetingId, $endMeetingParams->getMeetingID()); - $this->assertEquals($password, $endMeetingParams->getPassword()); // Test setters that are ignored by the constructor $endMeetingParams->setMeetingID($newId = $this->faker->uuid); - $endMeetingParams->setPassword($newPassword = $this->faker->password); $this->assertEquals($newId, $endMeetingParams->getMeetingID()); - $this->assertEquals($newPassword, $endMeetingParams->getPassword()); } } diff --git a/tests/unit/Parameters/GetMeetingInfoParametersTest.php b/tests/unit/Parameters/GetMeetingInfoParametersTest.php index 192f3205..df6bba99 100644 --- a/tests/unit/Parameters/GetMeetingInfoParametersTest.php +++ b/tests/unit/Parameters/GetMeetingInfoParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\GetMeetingInfoParameters; +use BigBlueButton\Tests\Common\TestCase; -class GetMeetingInfoParametersTest extends TestCase +final class GetMeetingInfoParametersTest extends TestCase { - public function testGetMeetingInfoParameters() + public function testGetMeetingInfoParameters(): void { $getMeetingInfoParams = new GetMeetingInfoParameters($meetingId = $this->faker->uuid); diff --git a/tests/unit/Parameters/GetRecordingTextTracksParametersTest.php b/tests/unit/Parameters/GetRecordingTextTracksParametersTest.php index ae46c8d5..22164b6b 100644 --- a/tests/unit/Parameters/GetRecordingTextTracksParametersTest.php +++ b/tests/unit/Parameters/GetRecordingTextTracksParametersTest.php @@ -1,5 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\GetRecordingTextTracksParameters; +use BigBlueButton\Tests\Common\TestCase; -class GetRecordingTextTracksParametersTest extends TestCase +final class GetRecordingTextTracksParametersTest extends TestCase { - public function testGetRecordingTextTracksParameters() + public function testGetRecordingTextTracksParameters(): void { $getRecordingTextTracksParams = new GetRecordingTextTracksParameters($recordId = $this->faker->uuid); diff --git a/tests/unit/Parameters/GetRecordingsParametersTest.php b/tests/unit/Parameters/GetRecordingsParametersTest.php index 355c5500..d3967a2d 100644 --- a/tests/unit/Parameters/GetRecordingsParametersTest.php +++ b/tests/unit/Parameters/GetRecordingsParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\GetRecordingsParameters; +use BigBlueButton\Tests\Common\TestCase; -class GetRecordingsParametersTest extends TestCase +final class GetRecordingsParametersTest extends TestCase { - public function testGetRecordingsParameters() + public function testGetRecordingsParameters(): void { $getRecordings = new GetRecordingsParameters(); $getRecordings->setMeetingID($meetingId = $this->faker->uuid); diff --git a/tests/unit/Parameters/HooksCreateParametersTest.php b/tests/unit/Parameters/HooksCreateParametersTest.php index 97769273..f8e344c0 100644 --- a/tests/unit/Parameters/HooksCreateParametersTest.php +++ b/tests/unit/Parameters/HooksCreateParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\HooksCreateParameters; +use BigBlueButton\Tests\Common\TestCase; -class HooksCreateParametersTest extends TestCase +final class HooksCreateParametersTest extends TestCase { - public function testHooksCreateParameters() + public function testHooksCreateParameters(): void { $hooksCreateParameters = new HooksCreateParameters($callBackUrl = $this->faker->url); diff --git a/tests/unit/Parameters/HooksDestroyParametersTest.php b/tests/unit/Parameters/HooksDestroyParametersTest.php index c3e5eddb..aa3ea0ff 100644 --- a/tests/unit/Parameters/HooksDestroyParametersTest.php +++ b/tests/unit/Parameters/HooksDestroyParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\HooksDestroyParameters; +use BigBlueButton\Tests\Common\TestCase; -class HooksDestroyParametersTest extends TestCase +final class HooksDestroyParametersTest extends TestCase { public function testHooksDestroyParameters(): void { - $hooksCreateParameters = new HooksDestroyParameters($hookId = $this->faker->numberBetween(1, 50)); + $hooksCreateParameters = new HooksDestroyParameters((string) $hookId = $this->faker->numberBetween(1, 50)); $this->assertEquals($hookId, $hooksCreateParameters->getHookID()); } diff --git a/tests/unit/Parameters/HooksListParametersTest.php b/tests/unit/Parameters/HooksListParametersTest.php new file mode 100644 index 00000000..5aa44d53 --- /dev/null +++ b/tests/unit/Parameters/HooksListParametersTest.php @@ -0,0 +1,40 @@ +. + */ + +namespace BigBlueButton\Tests\Unit\Parameters; + +use BigBlueButton\Parameters\HooksListParameters; +use BigBlueButton\Tests\Common\TestCase; + +final class HooksListParametersTest extends TestCase +{ + public function testHooksListParameters(): void + { + $hooksListParameters = new HooksListParameters(); + + $this->assertNull($hooksListParameters->getMeetingID()); + + // Test setters that are ignored by the constructor + $hooksListParameters->setMeetingID($meetingId = $this->faker->uuid); + $this->assertEquals($meetingId, $hooksListParameters->getMeetingID()); + } +} diff --git a/tests/unit/Parameters/InsertDocumentParametersTest.php b/tests/unit/Parameters/InsertDocumentParametersTest.php index d7acb3cd..d9bdd361 100644 --- a/tests/unit/Parameters/InsertDocumentParametersTest.php +++ b/tests/unit/Parameters/InsertDocumentParametersTest.php @@ -19,20 +19,26 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\InsertDocumentParameters; +use BigBlueButton\Tests\Common\TestCase; final class InsertDocumentParametersTest extends TestCase { - public function testIsMeetingRunningParameters(): void + public function testInsertingDocuments(): void { $meetingId = $this->faker->uuid; $params = new InsertDocumentParameters($meetingId); + // Adding presentations $params->addPresentation('http://localhost/foobar.png', 'Foobar.png'); $params->addPresentation('http://localhost/foobar.pdf', 'Foobar.pdf', true); $params->addPresentation('http://localhost/foobar.svg', 'Foobar.svg', true, false); + $params->addPresentation('http://localhost/demo.pdf', 'Demo.pdf', true); + + // Removing presentation + $params->removePresentation('http://localhost/demo.pdf'); $this->assertEquals($meetingId, $params->getMeetingID()); diff --git a/tests/unit/Parameters/IsMeetingRunningParametersTest.php b/tests/unit/Parameters/IsMeetingRunningParametersTest.php index 9e89b286..3c941408 100644 --- a/tests/unit/Parameters/IsMeetingRunningParametersTest.php +++ b/tests/unit/Parameters/IsMeetingRunningParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\IsMeetingRunningParameters; +use BigBlueButton\Tests\Common\TestCase; /** * Class IsMeetingRunningParametersTest. */ -class IsMeetingRunningParametersTest extends TestCase +final class IsMeetingRunningParametersTest extends TestCase { - public function testIsMeetingRunningParameters() + public function testIsMeetingRunningParameters(): void { $meetingId = $this->faker->uuid; $isRunningParams = new IsMeetingRunningParameters($meetingId); diff --git a/tests/unit/Parameters/JoinMeetingParametersTest.php b/tests/unit/Parameters/JoinMeetingParametersTest.php index 924ba035..4aa1f16b 100644 --- a/tests/unit/Parameters/JoinMeetingParametersTest.php +++ b/tests/unit/Parameters/JoinMeetingParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Enum\Role; +use BigBlueButton\Tests\Common\TestCase; -class JoinMeetingParametersTest extends TestCase +final class JoinMeetingParametersTest extends TestCase { - public function testJoinMeetingParameters() + public function testJoinMeetingParameters(): void { $params = $this->generateJoinMeetingParams(); + $params['role'] = Role::MODERATOR; $joinMeetingParams = $this->getJoinMeetingMock($params); $this->assertEquals($params['meetingID'], $joinMeetingParams->getMeetingID()); $this->assertEquals($params['fullName'], $joinMeetingParams->getFullName()); - $this->assertEquals($params['password'], $joinMeetingParams->getPassword()); + $this->assertEquals($params['role'], $joinMeetingParams->getRole()); $this->assertEquals($params['userID'], $joinMeetingParams->getUserID()); $this->assertEquals($params['webVoiceConf'], $joinMeetingParams->getWebVoiceConf()); $this->assertEquals($params['createTime'], $joinMeetingParams->getCreateTime()); + $this->assertEquals($params['errorRedirectUrl'], $joinMeetingParams->getErrorRedirectUrl()); $this->assertEquals($params['userdata-countrycode'], $joinMeetingParams->getUserData('countrycode')); $this->assertEquals($params['userdata-email'], $joinMeetingParams->getUserData('email')); $this->assertEquals($params['userdata-commercial'], $joinMeetingParams->getUserData('commercial')); @@ -41,21 +47,21 @@ public function testJoinMeetingParameters() // Test setters that are ignored by the constructor $joinMeetingParams->setMeetingID($newId = $this->faker->uuid); $joinMeetingParams->setFullName($newName = $this->faker->name); - $joinMeetingParams->setPassword($newPassword = $this->faker->password); - $joinMeetingParams->setConfigToken($configToken = $this->faker->md5); + $joinMeetingParams->setRole($newRole = Role::VIEWER); $joinMeetingParams->setAvatarURL($avatarUrl = $this->faker->url); $joinMeetingParams->setRedirect($redirect = $this->faker->boolean(50)); - $joinMeetingParams->setClientURL($clientUrl = $this->faker->url); - $joinMeetingParams->setJoinViaHtml5($joinViaHtml5 = $this->faker->boolean(50)); + $joinMeetingParams->setErrorRedirectUrl($newErrorRedirectUrl = $this->faker->url); $joinMeetingParams->setGuest($guest = $this->faker->boolean(50)); + $joinMeetingParams->setWebcamBackgroundURL($webcamBackgroundURL = $this->faker->url); + $joinMeetingParams->setBot($bot = $this->faker->boolean(50)); $this->assertEquals($newId, $joinMeetingParams->getMeetingID()); $this->assertEquals($newName, $joinMeetingParams->getFullName()); - $this->assertEquals($newPassword, $joinMeetingParams->getPassword()); - $this->assertEquals($configToken, $joinMeetingParams->getConfigToken()); + $this->assertEquals($newRole, $joinMeetingParams->getRole()); $this->assertEquals($avatarUrl, $joinMeetingParams->getAvatarURL()); $this->assertEquals($redirect, $joinMeetingParams->isRedirect()); - $this->assertEquals($clientUrl, $joinMeetingParams->getClientURL()); - $this->assertEquals($joinViaHtml5, $joinMeetingParams->isJoinViaHtml5()); + $this->assertEquals($newErrorRedirectUrl, $joinMeetingParams->getErrorRedirectUrl()); $this->assertEquals($guest, $joinMeetingParams->isGuest()); + $this->assertEquals($webcamBackgroundURL, $joinMeetingParams->getWebcamBackgroundURL()); + $this->assertEquals($bot, $joinMeetingParams->isBot()); } } diff --git a/tests/unit/Parameters/PublishRecordingsParametersTest.php b/tests/unit/Parameters/PublishRecordingsParametersTest.php index 3abfdf13..95cc3228 100644 --- a/tests/unit/Parameters/PublishRecordingsParametersTest.php +++ b/tests/unit/Parameters/PublishRecordingsParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\PublishRecordingsParameters; +use BigBlueButton\Tests\Common\TestCase; -class PublishRecordingsParametersTest extends TestCase +final class PublishRecordingsParametersTest extends TestCase { - public function testPublishRecordingsParameters() + public function testPublishRecordingsParameters(): void { $recordingId = $this->faker->uuid; $publish = $this->faker->boolean(50); @@ -33,7 +37,7 @@ public function testPublishRecordingsParameters() $this->assertEquals($publish, $publishRecording->isPublish()); // Test setters that are ignored by the constructor - $publishRecording->setRecordID($newRecordingId = !$this->faker->uuid); + $publishRecording->setRecordID($newRecordingId = $this->faker->uuid); $publishRecording->setPublish($publish = !$publish); $this->assertEquals($newRecordingId, $publishRecording->getRecordID()); $this->assertEquals($publish, $publishRecording->isPublish()); diff --git a/tests/unit/Parameters/PutRecordingTextTracksParametersTest.php b/tests/unit/Parameters/PutRecordingTextTracksParametersTest.php index 454cdffb..0e02f5b8 100644 --- a/tests/unit/Parameters/PutRecordingTextTracksParametersTest.php +++ b/tests/unit/Parameters/PutRecordingTextTracksParametersTest.php @@ -1,5 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Parameters\PutRecordingTextTrackParameters; +use BigBlueButton\Tests\Common\TestCase; -class PutRecordingTextTracksParametersTest extends TestCase +final class PutRecordingTextTracksParametersTest extends TestCase { - public function testPutRecordingTextTracksParameters() + public function testPutRecordingTextTracksParameters(): void { $getRecordingTextTracksParams = new PutRecordingTextTrackParameters( $recordId = $this->faker->uuid, diff --git a/tests/unit/Parameters/UpdateRecordingsParametersTest.php b/tests/unit/Parameters/UpdateRecordingsParametersTest.php index ad36c198..8f4eafa5 100644 --- a/tests/unit/Parameters/UpdateRecordingsParametersTest.php +++ b/tests/unit/Parameters/UpdateRecordingsParametersTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Parameters; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class UpdateRecordingsParametersTest extends TestCase +final class UpdateRecordingsParametersTest extends TestCase { - public function testUpdateRecordingsParameters() + public function testUpdateRecordingsParameters(): void { $params = $this->generateUpdateRecordingsParams(); $updateRecordingsParams = $this->getUpdateRecordingsParamsMock($params); diff --git a/tests/unit/Responses/ApiVersionResponseTest.php b/tests/unit/Responses/ApiVersionResponseTest.php index 7f59a122..a43c8401 100644 --- a/tests/unit/Responses/ApiVersionResponseTest.php +++ b/tests/unit/Responses/ApiVersionResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\ApiVersionResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class ApiVersionResponseTest extends TestCase +final class ApiVersionResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\ApiVersionResponse - */ - private $version; + private ApiVersionResponse $version; protected function setUp(): void { @@ -38,7 +38,7 @@ protected function setUp(): void $this->version = new ApiVersionResponse($xml); } - public function testApiVersionResponseContent() + public function testApiVersionResponseContent(): void { $this->assertEquals('SUCCESS', $this->version->getReturnCode()); $this->assertEquals('2.0', $this->version->getVersion()); @@ -46,7 +46,7 @@ public function testApiVersionResponseContent() $this->assertEquals('2.4-rc-7', $this->version->getBbbVersion()); } - public function testApiVersionResponseTypes() + public function testApiVersionResponseTypes(): void { $this->assertEachGetterValueIsString($this->version, ['getReturnCode', 'getVersion', 'getApiVersion', 'getBbbVersion']); } diff --git a/tests/unit/Responses/CreateMeetingResponseTest.php b/tests/unit/Responses/CreateMeetingResponseTest.php index 84198494..6d5d40f2 100644 --- a/tests/unit/Responses/CreateMeetingResponseTest.php +++ b/tests/unit/Responses/CreateMeetingResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\CreateMeetingResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class CreateMeetingResponseTest extends TestCase +final class CreateMeetingResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\CreateMeetingResponse - */ - private $meeting; + private CreateMeetingResponse $meeting; protected function setUp(): void { @@ -38,14 +38,15 @@ protected function setUp(): void $this->meeting = new CreateMeetingResponse($xml); } - public function testCreateMeetingResponseContent() + public function testCreateMeetingResponseContent(): void { + $this->assertTrue($this->meeting->success()); + $this->assertFalse($this->meeting->failed()); $this->assertEquals('SUCCESS', $this->meeting->getReturnCode()); + $this->assertEquals('random-1665177', $this->meeting->getMeetingId()); $this->assertEquals('1a6938c707cdf5d052958672d66c219c30690c47-1524212045514', $this->meeting->getInternalMeetingId()); $this->assertEquals('bbb-none', $this->meeting->getParentMeetingId()); - $this->assertEquals('tK6J5cJv3hMLNx5IBePa', $this->meeting->getAttendeePassword()); - $this->assertEquals('34Heu0uiZYqCZXX9C4m2', $this->meeting->getModeratorPassword()); $this->assertEquals(1453283819419, $this->meeting->getCreationTime()); $this->assertEquals(76286, $this->meeting->getVoiceBridge()); $this->assertEquals('Wed Jan 20 04:56:59 EST 2016', $this->meeting->getCreationDate()); @@ -54,15 +55,33 @@ public function testCreateMeetingResponseContent() $this->assertEquals(20, $this->meeting->getDuration()); $this->assertFalse($this->meeting->hasBeenForciblyEnded()); $this->assertEquals('duplicateWarning', $this->meeting->getMessageKey()); + $this->assertTrue($this->meeting->isDuplicate()); $this->assertEquals('This conference was already in existence and may currently be in progress.', $this->meeting->getMessage()); + + $this->assertFalse($this->meeting->isIdNotUnique()); } - public function testCreateMeetingResponseTypes() + public function testCreateMeetingResponseTypes(): void { - $this->assertEachGetterValueIsString($this->meeting, ['getReturnCode', 'getInternalMeetingId', 'getParentMeetingId', - 'getAttendeePassword', 'getModeratorPassword', 'getDialNumber', 'getCreationDate', ]); + $this->assertEachGetterValueIsString($this->meeting, [ + 'getReturnCode', 'getInternalMeetingId', 'getParentMeetingId', + 'getDialNumber', 'getCreationDate', + ]); $this->assertEachGetterValueIsDouble($this->meeting, ['getCreationTime']); $this->assertEachGetterValueIsInteger($this->meeting, ['getDuration', 'getVoiceBridge']); $this->assertEachGetterValueIsBoolean($this->meeting, ['hasUserJoined', 'hasBeenForciblyEnded']); } + + public function testIdNotUnique(): void + { + $xml = $this->loadXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'create_meeting_not_unique_error.xml'); + + $meeting = new CreateMeetingResponse($xml); + + $this->assertFalse($meeting->success()); + $this->assertTrue($meeting->failed()); + $this->assertEquals('FAILED', $meeting->getReturnCode()); + + $this->assertTrue($meeting->isIdNotUnique()); + } } diff --git a/tests/unit/Responses/DeleteRecordingsResponseTest.php b/tests/unit/Responses/DeleteRecordingsResponseTest.php index ae7416f5..39c9be87 100644 --- a/tests/unit/Responses/DeleteRecordingsResponseTest.php +++ b/tests/unit/Responses/DeleteRecordingsResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\DeleteRecordingsResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class DeleteRecordingsResponseTest extends TestCase +final class DeleteRecordingsResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\DeleteRecordingsResponse - */ - private $delete; + private DeleteRecordingsResponse $delete; protected function setUp(): void { @@ -38,15 +38,25 @@ protected function setUp(): void $this->delete = new DeleteRecordingsResponse($xml); } - public function testDeleteRecordingsResponseContent() + public function testDeleteRecordingsResponseContent(): void { $this->assertEquals('SUCCESS', $this->delete->getReturnCode()); $this->assertTrue($this->delete->isDeleted()); } - public function testDeleteRecordingsResponseTypes() + public function testDeleteRecordingsResponseTypes(): void { $this->assertEachGetterValueIsString($this->delete, ['getReturnCode']); $this->assertEachGetterValueIsBoolean($this->delete, ['isDeleted']); } + + public function testNotFoundError(): void + { + $xml = $this->loadXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'not_found_error.xml'); + + $delete = new DeleteRecordingsResponse($xml); + + $this->assertTrue($delete->failed()); + $this->assertTrue($delete->isNotFound()); + } } diff --git a/tests/unit/Responses/EndMeetingResponseTest.php b/tests/unit/Responses/EndMeetingResponseTest.php index 37ecefb9..1e1300e1 100644 --- a/tests/unit/Responses/EndMeetingResponseTest.php +++ b/tests/unit/Responses/EndMeetingResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\EndMeetingResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class EndMeetingResponseTest extends TestCase +final class EndMeetingResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\EndMeetingResponse - */ - private $end; + private EndMeetingResponse $end; protected function setUp(): void { @@ -38,14 +38,14 @@ protected function setUp(): void $this->end = new EndMeetingResponse($xml); } - public function testEndMeetingResponseContent() + public function testEndMeetingResponseContent(): void { $this->assertEquals('SUCCESS', $this->end->getReturnCode()); $this->assertEquals('sentEndMeetingRequest', $this->end->getMessageKey()); $this->assertEquals('A request to end the meeting was sent. Please wait a few seconds, and then use the getMeetingInfo or isMeetingRunning API calls to verify that it was ended.', $this->end->getMessage()); } - public function testEndMeetingResponseTypes() + public function testEndMeetingResponseTypes(): void { $this->assertEachGetterValueIsString($this->end, ['getReturnCode', 'getMessageKey', 'getMessage']); } diff --git a/tests/unit/Responses/GetMeetingInfoResponseTest.php b/tests/unit/Responses/GetMeetingInfoResponseTest.php index c6cf2696..6ab4e8fd 100644 --- a/tests/unit/Responses/GetMeetingInfoResponseTest.php +++ b/tests/unit/Responses/GetMeetingInfoResponseTest.php @@ -1,5 +1,11 @@ . */ -class GetMeetingInfoResponseTest extends \BigBlueButton\TestCase +final class GetMeetingInfoResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\GetMeetingInfoResponse - */ - private $meetingInfo; + private GetMeetingInfoResponse $meetingInfo; protected function setUp(): void { @@ -30,12 +33,12 @@ protected function setUp(): void $xml = $this->loadXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'get_meeting_info.xml'); - $this->meetingInfo = new \BigBlueButton\Responses\GetMeetingInfoResponse($xml); + $this->meetingInfo = new GetMeetingInfoResponse($xml); } - public function testGetMeetingInfoResponseContent() + public function testGetMeetingInfoResponseContent(): void { - $this->assertInstanceOf('BigBlueButton\Core\Meeting', $this->meetingInfo->getMeeting()); + $this->assertInstanceOf(Meeting::class, $this->meetingInfo->getMeeting()); $this->assertCount(4, $this->meetingInfo->getMeeting()->getAttendees()); $this->assertEquals('SUCCESS', $this->meetingInfo->getReturnCode()); @@ -47,8 +50,6 @@ public function testGetMeetingInfoResponseContent() $this->assertEquals('Tue Jan 19 07:25:17 EST 2016', $info->getCreationDate()); $this->assertEquals(70100, $info->getVoiceBridge()); $this->assertEquals('613-555-1234', $info->getDialNumber()); - $this->assertEquals('dbfc7207321527bbb870c82028', $info->getAttendeePassword()); - $this->assertEquals('4bfbbeeb4a65cacaefe3676633', $info->getModeratorPassword()); $this->assertTrue($info->isRunning()); $this->assertEquals(20, $info->getDuration()); $this->assertTrue($info->hasUserJoined()); @@ -66,7 +67,7 @@ public function testGetMeetingInfoResponseContent() $this->assertTrue($info->isBreakout()); } - public function testMeetingAttendeeContent() + public function testMeetingAttendeeContent(): void { $this->assertCount(4, $this->meetingInfo->getMeeting()->getAttendees()); @@ -118,15 +119,19 @@ public function testMeetingViewers(): void $this->assertEquals('VIEWER', $secondViewer->getRole()); } - public function testGetMeetingInfoResponseTypes() + public function testGetMeetingInfoResponseTypes(): void { $info = $this->meetingInfo->getMeeting(); - $this->assertEachGetterValueIsString($info, ['getMeetingName', 'getMeetingId', 'getInternalMeetingId', - 'getModeratorPassword', 'getAttendeePassword', 'getCreationDate', 'getDialNumber', ]); + $this->assertEachGetterValueIsString($info, [ + 'getMeetingName', 'getMeetingId', 'getInternalMeetingId', + 'getCreationDate', 'getDialNumber', + ]); - $this->assertEachGetterValueIsInteger($info, ['getVoiceBridge', 'getDuration', 'getParticipantCount', - 'getListenerCount', 'getVoiceParticipantCount', 'getVideoCount', 'getMaxUsers', 'getModeratorCount', ]); + $this->assertEachGetterValueIsInteger($info, [ + 'getVoiceBridge', 'getDuration', 'getParticipantCount', + 'getListenerCount', 'getVoiceParticipantCount', 'getVideoCount', 'getMaxUsers', 'getModeratorCount', + ]); $this->assertEachGetterValueIsDouble($info, ['getStartTime', 'getEndTime', 'getCreationTime']); @@ -138,7 +143,7 @@ public function testGetMeetingInfoResponseTypes() $this->assertEachGetterValueIsBoolean($anAttendee, ['isPresenter', 'isListeningOnly', 'hasJoinedVoice', 'hasVideo']); } - public function testGetMeetingInfoMetadataContent() + public function testGetMeetingInfoMetadataContent(): void { $metas = $this->meetingInfo->getMeeting()->getMetas(); diff --git a/tests/unit/Responses/GetMeetingsResponseTest.php b/tests/unit/Responses/GetMeetingsResponseTest.php index 2f77ce3d..f39df44a 100644 --- a/tests/unit/Responses/GetMeetingsResponseTest.php +++ b/tests/unit/Responses/GetMeetingsResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\GetMeetingsResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class GetMeetingsResponseTest extends TestCase +final class GetMeetingsResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\GetMeetingsResponse - */ - private $meetings; + private GetMeetingsResponse $meetings; protected function setUp(): void { @@ -38,7 +38,7 @@ protected function setUp(): void $this->meetings = new GetMeetingsResponse($xml); } - public function testGetMeetingsResponseContent() + public function testGetMeetingsResponseContent(): void { $this->assertEquals('SUCCESS', $this->meetings->getReturnCode()); @@ -52,8 +52,6 @@ public function testGetMeetingsResponseContent() $this->assertEquals('Tue Jan 19 08:27:55 EST 2016', $aMeeting->getCreationDate()); $this->assertEquals(49518, $aMeeting->getVoiceBridge()); $this->assertEquals('580.124.3937x93615', $aMeeting->getDialNumber()); - $this->assertEquals('f~kxYJeAV~G?Jb+E:ggn', $aMeeting->getAttendeePassword()); - $this->assertEquals('n:"zWc##Bi.y,d^s,mMF', $aMeeting->getModeratorPassword()); $this->assertFalse($aMeeting->hasBeenForciblyEnded()); $this->assertTrue($aMeeting->isRunning()); $this->assertEquals(5, $aMeeting->getParticipantCount()); @@ -68,21 +66,22 @@ public function testGetMeetingsResponseContent() $this->assertEquals('http://www.muller.biz/autem-dolor-aut-nam-doloribus-molestiae', $aMeeting->getMetas()['endcallbackurl']); } - public function testGetMeetingsResponseTypes() + public function testGetMeetingsResponseTypes(): void { $this->assertEachGetterValueIsString($this->meetings, ['getReturnCode']); $aMeeting = $this->meetings->getMeetings()[2]; - $this->assertEachGetterValueIsString($aMeeting, ['getMeetingId', 'getMeetingName', 'getCreationDate', 'getDialNumber', - 'getAttendeePassword', 'getModeratorPassword', ]); + $this->assertEachGetterValueIsString($aMeeting, ['getMeetingId', 'getMeetingName', 'getCreationDate', 'getDialNumber']); $this->assertEachGetterValueIsDouble($aMeeting, ['getCreationTime']); - $this->assertEachGetterValueIsInteger($aMeeting, ['getVoiceBridge', 'getParticipantCount', 'getListenerCount', - 'getVoiceParticipantCount', 'getVideoCount', 'getDuration', ]); + $this->assertEachGetterValueIsInteger($aMeeting, [ + 'getVoiceBridge', 'getParticipantCount', 'getListenerCount', + 'getVoiceParticipantCount', 'getVideoCount', 'getDuration', + ]); $this->assertEachGetterValueIsBoolean($aMeeting, ['hasBeenForciblyEnded', 'isRunning', 'hasUserJoined']); } - public function testGetMeetingsNoMeetings() + public function testGetMeetingsNoMeetings(): void { // scalelite response no meetings $xml = simplexml_load_string('SUCCESSnoMeetingsNo meetings were found on this server.'); diff --git a/tests/unit/Responses/GetRecordingsResponseTest.php b/tests/unit/Responses/GetRecordingsResponseTest.php index 85dfcd4e..d95d8e6b 100644 --- a/tests/unit/Responses/GetRecordingsResponseTest.php +++ b/tests/unit/Responses/GetRecordingsResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\GetRecordingsResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class GetRecordingsResponseTest extends TestCase +final class GetRecordingsResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\GetRecordingsResponse - */ - private $records; + private GetRecordingsResponse $records; protected function setUp(): void { @@ -38,7 +38,7 @@ protected function setUp(): void $this->records = new GetRecordingsResponse($xml); } - public function testGetRecordingResponseContent() + public function testGetRecordingResponseContent(): void { $this->assertEquals('SUCCESS', $this->records->getReturnCode()); @@ -60,14 +60,14 @@ public function testGetRecordingResponseContent() $this->assertEquals(9, \count($aRecord->getMetas())); } - public function testRecordMetadataContent() + public function testRecordMetadataContent(): void { $metas = $this->records->getRecords()[4]->getMetas(); $this->assertEquals('moodle-mod_bigbluebuttonbn (2015080611)', $metas['bbb-origin-tag']); } - public function testGetRecordingResponseTypes() + public function testGetRecordingResponseTypes(): void { $this->assertEachGetterValueIsString($this->records, ['getReturnCode']); @@ -111,5 +111,25 @@ public function testImagePreviews(): void $this->assertEquals('Welcome to', $previews[0]->getAlt()); $this->assertEquals('https://demo.bigbluebutton.org/presentation/ffbfc4cc24428694e8b53a4e144f414052431693-1530718721124/presentation/d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1530718721134/thumbnails/thumb-1.png', $previews[0]->getUrl()); + $this->assertEquals(176, $previews[0]->getWidth()); + $this->assertEquals(136, $previews[0]->getHeight()); + + // Load previews again, check if same instance is returned (caching) + $newPreviews = $formats[1]->getImagePreviews(); + $this->assertTrue($previews[0] === $newPreviews[0]); + } + + public function testHasNoRecordings(): void + { + $xml = ' + SUCCESS + + noRecordings + There are no recordings for the meeting(s). + '; + + $response = new GetRecordingsResponse(simplexml_load_string($xml)); + + $this->assertTrue($response->hasNoRecordings()); } } diff --git a/tests/unit/Responses/GetRecordingsTextTracksResponseTest.php b/tests/unit/Responses/GetRecordingsTextTracksResponseTest.php index eb660884..ef3c7cc6 100644 --- a/tests/unit/Responses/GetRecordingsTextTracksResponseTest.php +++ b/tests/unit/Responses/GetRecordingsTextTracksResponseTest.php @@ -1,16 +1,15 @@ tracks = new GetRecordingTextTracksResponse($json); } - public function testGetRecordingTextTracksResponseContent() + public function testGetRecordingTextTracksResponseContent(): void { $this->assertEquals(GetRecordingTextTracksResponse::SUCCESS, $this->tracks->getReturnCode()); $this->assertTrue($this->tracks->success()); @@ -30,7 +29,7 @@ public function testGetRecordingTextTracksResponseContent() $this->assertCount(2, $this->tracks->getTracks()); } - public function testGetRecordingTextTracksResponseTypes() + public function testGetRecordingTextTracksResponseTypes(): void { $this->assertEachGetterValueIsString($this->tracks, ['getReturnCode']); @@ -41,16 +40,16 @@ public function testGetRecordingTextTracksResponseTypes() $secondTracks = $this->tracks->getTracks()[1]; $this->assertEachGetterValueIsString($secondTracks, [ - 'getHref', - 'getKind', - 'getLabel', - 'getLang', - 'getSource', - ] + 'getHref', + 'getKind', + 'getLabel', + 'getLang', + 'getSource', + ] ); } - public function testGetRecordingTextTracksResponseValues() + public function testGetRecordingTextTracksResponseValues(): void { $secondTracks = $this->tracks->getTracks()[1]; diff --git a/tests/unit/Responses/HooksCreateResponseTest.php b/tests/unit/Responses/HooksCreateResponseTest.php index 440e5965..77a35d1a 100644 --- a/tests/unit/Responses/HooksCreateResponseTest.php +++ b/tests/unit/Responses/HooksCreateResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\HooksCreateResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class HooksCreateResponseTest extends TestCase +final class HooksCreateResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\HooksCreateResponse - */ - private $createResponse; + private HooksCreateResponse $createResponse; protected function setUp(): void { @@ -38,7 +38,7 @@ protected function setUp(): void $this->createResponse = new HooksCreateResponse($xml); } - public function testHooksCreateResponseContent() + public function testHooksCreateResponseContent(): void { $this->assertEquals('SUCCESS', $this->createResponse->getReturnCode()); $this->assertEquals(1, $this->createResponse->getHookId()); @@ -46,7 +46,7 @@ public function testHooksCreateResponseContent() $this->assertFalse($this->createResponse->hasRawData()); } - public function testHooksCreateResponseTypes() + public function testHooksCreateResponseTypes(): void { $this->assertEachGetterValueIsString($this->createResponse, ['getReturnCode']); $this->assertEachGetterValueIsInteger($this->createResponse, ['getHookId']); diff --git a/tests/unit/Responses/HooksDestroyResponseTest.php b/tests/unit/Responses/HooksDestroyResponseTest.php index 0e0eb047..7704ee1a 100644 --- a/tests/unit/Responses/HooksDestroyResponseTest.php +++ b/tests/unit/Responses/HooksDestroyResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\HooksDestroyResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class HooksDestroyResponseTest extends TestCase +final class HooksDestroyResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\HooksDestroyResponse - */ - private $destroyResponse; + private HooksDestroyResponse $destroyResponse; protected function setUp(): void { @@ -38,15 +38,33 @@ protected function setUp(): void $this->destroyResponse = new HooksDestroyResponse($xml); } - public function testHooksDestroyResponseContent() + public function testHooksDestroyResponseContent(): void { $this->assertEquals('SUCCESS', $this->destroyResponse->getReturnCode()); $this->assertTrue($this->destroyResponse->removed()); } - public function testHooksDestroyResponseTypes() + public function testHooksDestroyResponseTypes(): void { $this->assertEachGetterValueIsString($this->destroyResponse, ['getReturnCode']); $this->assertEachGetterValueIsBoolean($this->destroyResponse, ['removed']); } + + public function testHookDestroyMissingHook(): void + { + $xml = simplexml_load_string('FAILEDdestroyMissingHookThe hook informed was not found.'); + + $destroyResponse = new HooksDestroyResponse($xml); + $this->assertTrue($destroyResponse->failed()); + $this->assertTrue($destroyResponse->isMissingHook()); + } + + public function testHookDestroyHookError(): void + { + $xml = simplexml_load_string('FAILEDdestroyHookErrorAn error happened while removing your hook. Check the logs.'); + + $destroyResponse = new HooksDestroyResponse($xml); + $this->assertTrue($destroyResponse->failed()); + $this->assertTrue($destroyResponse->isHookError()); + } } diff --git a/tests/unit/Responses/HooksListResponseTest.php b/tests/unit/Responses/HooksListResponseTest.php index ea3d2745..8395fd19 100644 --- a/tests/unit/Responses/HooksListResponseTest.php +++ b/tests/unit/Responses/HooksListResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\HooksListResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class HooksListResponseTest extends TestCase +final class HooksListResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\HooksListResponse - */ - private $listResponse; + private HooksListResponse $listResponse; protected function setUp(): void { @@ -38,7 +38,7 @@ protected function setUp(): void $this->listResponse = new HooksListResponse($xml); } - public function testHooksListResponseContent() + public function testHooksListResponseContent(): void { $this->assertEquals('SUCCESS', $this->listResponse->getReturnCode()); $this->assertCount(2, $this->listResponse->getHooks()); @@ -52,7 +52,7 @@ public function testHooksListResponseContent() $this->assertFalse($aHook->hasRawData()); } - public function testHooksListResponseTypes() + public function testHooksListResponseTypes(): void { $this->assertEachGetterValueIsString($this->listResponse, ['getReturnCode']); diff --git a/tests/unit/Responses/InsertDocumentResponseTest.php b/tests/unit/Responses/InsertDocumentResponseTest.php index bf9dc225..01d1012e 100644 --- a/tests/unit/Responses/InsertDocumentResponseTest.php +++ b/tests/unit/Responses/InsertDocumentResponseTest.php @@ -19,17 +19,14 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\InsertDocumentResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class InsertDocumentResponseTest extends TestCase +final class InsertDocumentResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\IsMeetingRunningResponse - */ - private $running; + private InsertDocumentResponse $running; protected function setUp(): void { diff --git a/tests/unit/Responses/IsMeetingRunningResponseTest.php b/tests/unit/Responses/IsMeetingRunningResponseTest.php index 5e4a2f89..70dce13f 100644 --- a/tests/unit/Responses/IsMeetingRunningResponseTest.php +++ b/tests/unit/Responses/IsMeetingRunningResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\IsMeetingRunningResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class IsMeetingRunningResponseTest extends TestCase +final class IsMeetingRunningResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\IsMeetingRunningResponse - */ - private $running; + private IsMeetingRunningResponse $running; protected function setUp(): void { @@ -38,7 +38,7 @@ protected function setUp(): void $this->running = new IsMeetingRunningResponse($xml); } - public function testIsMeetingRunningResponseContent() + public function testIsMeetingRunningResponseContent(): void { $this->assertEquals('SUCCESS', $this->running->getReturnCode()); $this->assertTrue($this->running->isRunning()); @@ -46,7 +46,7 @@ public function testIsMeetingRunningResponseContent() $this->assertEquals('SUCCESStrue', $this->minifyString($this->running->getRawXml()->asXML())); } - public function testIsMeetingRunningResponseTypes() + public function testIsMeetingRunningResponseTypes(): void { $this->assertEachGetterValueIsString($this->running, ['getReturnCode']); $this->assertEachGetterValueIsBoolean($this->running, ['isRunning']); diff --git a/tests/unit/Responses/JoinMeetingResponseTest.php b/tests/unit/Responses/JoinMeetingResponseTest.php index 0178dd55..836e3060 100644 --- a/tests/unit/Responses/JoinMeetingResponseTest.php +++ b/tests/unit/Responses/JoinMeetingResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\JoinMeetingResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class JoinMeetingResponseTest extends TestCase +final class JoinMeetingResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\JoinMeetingResponse - */ - private $joinMeeting; + private JoinMeetingResponse $joinMeeting; protected function setUp(): void { @@ -38,7 +38,7 @@ protected function setUp(): void $this->joinMeeting = new JoinMeetingResponse($xml); } - public function testJoinMeetingResponseContent() + public function testJoinMeetingResponseContent(): void { $this->assertEquals('SUCCESS', $this->joinMeeting->getReturnCode()); $this->assertEquals('successfullyJoined', $this->joinMeeting->getMessageKey()); @@ -51,7 +51,7 @@ public function testJoinMeetingResponseContent() $this->assertEquals('https://bigblubutton-server.sample/client/BigBlueButton.html?sessionToken=0wzsph6uaelwc68z', $this->joinMeeting->getUrl()); } - public function testJoinMeetingResponseTypes() + public function testJoinMeetingResponseTypes(): void { $this->assertEachGetterValueIsString($this->joinMeeting, ['getReturnCode', 'getMessageKey', 'getMessage', 'getMeetingId', 'getUserId', 'getAuthToken', 'getSessionToken', 'getGuestStatus', 'getUrl']); } diff --git a/tests/unit/Responses/PublishRecordingsResponseTest.php b/tests/unit/Responses/PublishRecordingsResponseTest.php index 105a6792..b8fe8727 100644 --- a/tests/unit/Responses/PublishRecordingsResponseTest.php +++ b/tests/unit/Responses/PublishRecordingsResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\PublishRecordingsResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class PublishRecordingsResponseTest extends TestCase +final class PublishRecordingsResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\PublishRecordingsResponse - */ - private $publish; + private PublishRecordingsResponse $publish; protected function setUp(): void { @@ -38,15 +38,25 @@ protected function setUp(): void $this->publish = new PublishRecordingsResponse($xml); } - public function testPublishRecordingsResponseContent() + public function testPublishRecordingsResponseContent(): void { $this->assertEquals('SUCCESS', $this->publish->getReturnCode()); $this->assertTrue($this->publish->isPublished()); } - public function testPublishRecordingsResponseTypes() + public function testPublishRecordingsResponseTypes(): void { $this->assertEachGetterValueIsString($this->publish, ['getReturnCode']); $this->assertEachGetterValueIsBoolean($this->publish, ['isPublished']); } + + public function testNotFoundError(): void + { + $xml = $this->loadXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'not_found_error.xml'); + + $publish = new PublishRecordingsResponse($xml); + + $this->assertTrue($publish->failed()); + $this->assertTrue($publish->isNotFound()); + } } diff --git a/tests/unit/Responses/PutRecordingTextTrackResponseTest.php b/tests/unit/Responses/PutRecordingTextTrackResponseTest.php new file mode 100644 index 00000000..8b6b9a34 --- /dev/null +++ b/tests/unit/Responses/PutRecordingTextTrackResponseTest.php @@ -0,0 +1,147 @@ +. + */ + +namespace BigBlueButton\Tests\Unit\Responses; + +use BigBlueButton\Responses\PutRecordingTextTrackResponse; +use BigBlueButton\Tests\Common\TestCase; + +final class PutRecordingTextTrackResponseTest extends TestCase +{ + public function testUploadSuccess(): void + { + $json = '{ + "response": { + "messageKey": "upload_text_track_success", + "message": "Text track uploaded successfully", + "recordId": "baz", + "returncode": "SUCCESS" + } + }'; + + $response = new PutRecordingTextTrackResponse($json); + + $this->assertTrue($response->success()); + $this->assertEquals('baz', $response->getRecordID()); + $this->assertTrue($response->isUploadTrackSuccess()); + } + + public function testUploadFailed(): void + { + $json = '{ + "response": { + "messageKey": "upload_text_track_failed", + "message": "Text track upload failed.", + "recordId": "baz", + "returncode": "FAILED" + } + }'; + + $response = new PutRecordingTextTrackResponse($json); + + $this->assertTrue($response->failed()); + $this->assertEquals('baz', $response->getRecordID()); + $this->assertTrue($response->isUploadTrackFailed()); + } + + public function testUploadEmpty(): void + { + $json = '{ + "response": { + "messageKey": "empty_uploaded_text_track", + "message": "Empty uploaded text track.", + "returncode": "FAILED" + } + }'; + + $response = new PutRecordingTextTrackResponse($json); + + $this->assertTrue($response->failed()); + $this->assertTrue($response->isUploadTrackEmpty()); + } + + public function testNoRecordings(): void + { + $json = '{ + "response": { + "messageKey": "noRecordings", + "message": "No recording was found for meeting-id-1234.", + "returncode": "FAILED" + } + }'; + + $response = new PutRecordingTextTrackResponse($json); + + $this->assertTrue($response->failed()); + $this->assertTrue($response->isNoRecordings()); + } + + public function testInvalidLang(): void + { + $json = '{ + "response": { + "messageKey": "invalidLang", + "message": "Malformed lang param, received=english", + "returncode": "FAILED" + } + }'; + + $response = new PutRecordingTextTrackResponse($json); + + $this->assertTrue($response->failed()); + $this->assertTrue($response->isInvalidLang()); + } + + public function testInvalidKind(): void + { + $json = '{ + "response": { + "messageKey": "invalidKind", + "message": "Invalid kind parameter, expected=\'subtitles|captions\' actual=somethingelse", + "returncode": "FAILED" + } + }'; + + $response = new PutRecordingTextTrackResponse($json); + + $this->assertTrue($response->failed()); + $this->assertTrue($response->isInvalidKind()); + } + + public function testHandleMissingJsonKeys(): void + { + $json = '{}'; + + $response = new PutRecordingTextTrackResponse($json); + + $this->assertFalse($response->success()); + $this->assertFalse($response->failed()); + $this->assertFalse($response->hasChecksumError()); + $this->assertEquals('', $response->getRecordID()); + $this->assertFalse($response->isUploadTrackSuccess()); + $this->assertFalse($response->isUploadTrackFailed()); + $this->assertFalse($response->isUploadTrackEmpty()); + $this->assertFalse($response->isNoRecordings()); + $this->assertFalse($response->isInvalidLang()); + $this->assertFalse($response->isInvalidKind()); + } +} diff --git a/tests/unit/Responses/UpdateRecordingsResponseTest.php b/tests/unit/Responses/UpdateRecordingsResponseTest.php index 76e61026..7f78c536 100644 --- a/tests/unit/Responses/UpdateRecordingsResponseTest.php +++ b/tests/unit/Responses/UpdateRecordingsResponseTest.php @@ -1,4 +1,7 @@ . */ -namespace BigBlueButton\Parameters; +namespace BigBlueButton\Tests\Unit\Responses; use BigBlueButton\Responses\UpdateRecordingsResponse; -use BigBlueButton\TestCase; +use BigBlueButton\Tests\Common\TestCase; -class UpdateRecordingsResponseTest extends TestCase +final class UpdateRecordingsResponseTest extends TestCase { - /** - * @var \BigBlueButton\Responses\UpdateRecordingsResponse - */ - private $update; + private UpdateRecordingsResponse $update; protected function setUp(): void { @@ -38,15 +38,25 @@ protected function setUp(): void $this->update = new UpdateRecordingsResponse($xml); } - public function testUpdateRecordingsResponseContent() + public function testUpdateRecordingsResponseContent(): void { $this->assertEquals('SUCCESS', $this->update->getReturnCode()); $this->assertTrue($this->update->isUpdated()); } - public function testUpdateRecordingsResponseTypes() + public function testUpdateRecordingsResponseTypes(): void { $this->assertEachGetterValueIsString($this->update, ['getReturnCode']); $this->assertEachGetterValueIsBoolean($this->update, ['isUpdated']); } + + public function testNotFoundError(): void + { + $xml = $this->loadXmlFile(__DIR__.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'..'.\DIRECTORY_SEPARATOR.'fixtures'.\DIRECTORY_SEPARATOR.'not_found_error.xml'); + + $update = new UpdateRecordingsResponse($xml); + + $this->assertTrue($update->failed()); + $this->assertTrue($update->isNotFound()); + } } diff --git a/tests/unit/Util/ArrayHelperTest.php b/tests/unit/Util/ArrayHelperTest.php index 16e43593..9ea33908 100644 --- a/tests/unit/Util/ArrayHelperTest.php +++ b/tests/unit/Util/ArrayHelperTest.php @@ -19,8 +19,9 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Util; +namespace BigBlueButton\Tests\Unit\Util; +use BigBlueButton\Util\ArrayHelper; use PHPUnit\Framework\TestCase; /** @@ -28,6 +29,7 @@ */ final class ArrayHelperTest extends TestCase { + /** @return iterable> */ public function provideArrays(): iterable { yield 'simple flat arrays' => [ @@ -58,6 +60,10 @@ public function provideArrays(): iterable /** * @dataProvider provideArrays + * + * @param array $input1 + * @param array $input2 + * @param array $output */ public function testMergeRecursive(array $input1, array $input2, bool $reorderNested, array $output): void { diff --git a/tests/unit/Util/SimpleXMLElementExtendedTest.php b/tests/unit/Util/SimpleXMLElementExtendedTest.php new file mode 100644 index 00000000..c29a09c7 --- /dev/null +++ b/tests/unit/Util/SimpleXMLElementExtendedTest.php @@ -0,0 +1,46 @@ +. + */ + +namespace BigBlueButton\Tests\Unit\Util; + +use BigBlueButton\Tests\Common\TestCase; +use BigBlueButton\Util\SimpleXMLElementExtended; + +/** + * @covers \BigBlueButton\Util\SimpleXMLElementExtended + */ +final class SimpleXMLElementExtendedTest extends TestCase +{ + /** + * Test adding a child element with CDATA content. + */ + public function testAddChildWithCData(): void + { + $xml = new SimpleXMLElementExtended(''); + $module = $xml->addChildWithCData('module', '{"foo": {"foo": "baa"}}'); + $module->addAttribute('name', 'clientSettingsOverride'); + + $expected = ' +'; + + $this->assertXmlStringEqualsXmlString($expected, $xml->asXML()); + } +} diff --git a/tests/unit/Util/UrlBuilderTest.php b/tests/unit/Util/UrlBuilderTest.php index facce75d..0c6acc94 100644 --- a/tests/unit/Util/UrlBuilderTest.php +++ b/tests/unit/Util/UrlBuilderTest.php @@ -19,8 +19,10 @@ * along with littleredbutton/bigbluebutton-api-php. If not, see . */ -namespace BigBlueButton\Util; +namespace BigBlueButton\Tests\Unit\Util; +use BigBlueButton\Enum\HashingAlgorithm; +use BigBlueButton\Util\UrlBuilder; use PHPUnit\Framework\TestCase; /** @@ -30,7 +32,8 @@ final class UrlBuilderTest extends TestCase { public function testBuildUrl(): void { - $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/'); + // Test with sha1 hash algorithm + $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/', HashingAlgorithm::SHA_1); // echo sha1('getMeetings' . 'foo=bar&baz=bazinga' . 'AFFE'); $this->assertSame( @@ -38,11 +41,21 @@ public function testBuildUrl(): void $urlBuilder->buildUrl('getMeetings', 'foo=bar&baz=bazinga'), 'signed URL is OK' ); + + // Test with sha256 hash algorithm + $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/', HashingAlgorithm::SHA_256); + + // echo hash('sha256', 'getMeetings' . 'foo=bar&baz=bazinga' . 'AFFE'); + $this->assertSame( + 'https://bbb.example/bigbluebutton/api/getMeetings?foo=bar&baz=bazinga&checksum=e93a022a742425259bf3acec803ad8b4e428e7653b66bfecfa60d935a04bcc3b', + $urlBuilder->buildUrl('getMeetings', 'foo=bar&baz=bazinga'), + 'signed URL is OK' + ); } public function testBuildUrlWithEmptyParams(): void { - $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/'); + $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/', HashingAlgorithm::SHA_1); // echo sha1('getMeetings' . '' . 'AFFE'); $this->assertSame( @@ -54,7 +67,7 @@ public function testBuildUrlWithEmptyParams(): void public function testBuildUrlWithoutAppend(): void { - $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/'); + $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/', HashingAlgorithm::SHA_1); $this->assertSame( 'https://bbb.example/bigbluebutton/api/getMeetings', @@ -65,7 +78,7 @@ public function testBuildUrlWithoutAppend(): void public function testBuildQs(): void { - $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/'); + $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/', HashingAlgorithm::SHA_1); // echo sha1('getMeetings' . 'foo=bar&baz=bazinga' . 'AFFE'); $this->assertSame( @@ -77,7 +90,7 @@ public function testBuildQs(): void public function testBuildQsWithEmptyParams(): void { - $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/'); + $urlBuilder = new UrlBuilder('AFFE', 'https://bbb.example/bigbluebutton/', HashingAlgorithm::SHA_1); // echo sha1('getMeetings' . '' . 'AFFE'); $this->assertSame( diff --git a/tools/.php-coveralls/composer.json b/tools/.php-coveralls/composer.json deleted file mode 100644 index 79cd2252..00000000 --- a/tools/.php-coveralls/composer.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "require": { - "php-coveralls/php-coveralls": "^2.4.0" - } -} diff --git a/tools/.phpstan/.gitignore b/tools/.phpstan/.gitignore new file mode 100644 index 00000000..57872d0f --- /dev/null +++ b/tools/.phpstan/.gitignore @@ -0,0 +1 @@ +/vendor/ diff --git a/tools/.phpstan/composer.json b/tools/.phpstan/composer.json new file mode 100644 index 00000000..90c99dfd --- /dev/null +++ b/tools/.phpstan/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "phpstan/phpstan": "^1.10" + } +} diff --git a/tools/.phpstan/composer.lock b/tools/.phpstan/composer.lock new file mode 100644 index 00000000..2c3ffe96 --- /dev/null +++ b/tools/.phpstan/composer.lock @@ -0,0 +1,77 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "14812c2a05a5972f00f9d67abbd710a9", + "packages": [ + { + "name": "phpstan/phpstan", + "version": "1.12.3", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0fcbf194ab63d8159bb70d9aa3e1350051632009", + "reference": "0fcbf194ab63d8159bb70d9aa3e1350051632009", + "shasum": "" + }, + "require": { + "php": "^7.2|^8.0" + }, + "conflict": { + "phpstan/phpstan-shim": "*" + }, + "bin": [ + "phpstan", + "phpstan.phar" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "keywords": [ + "dev", + "static analysis" + ], + "support": { + "docs": "https://phpstan.org/user-guide/getting-started", + "forum": "https://github.com/phpstan/phpstan/discussions", + "issues": "https://github.com/phpstan/phpstan/issues", + "security": "https://github.com/phpstan/phpstan/security/policy", + "source": "https://github.com/phpstan/phpstan-src" + }, + "funding": [ + { + "url": "https://github.com/ondrejmirtes", + "type": "github" + }, + { + "url": "https://github.com/phpstan", + "type": "github" + } + ], + "time": "2024-09-09T08:10:35+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/tools/.phpunit/composer.json b/tools/.phpunit/composer.json index 18a43fc8..4db33099 100644 --- a/tools/.phpunit/composer.json +++ b/tools/.phpunit/composer.json @@ -1,6 +1,6 @@ { "require": { - "phpunit/phpunit": "^8", - "fakerphp/faker": "^1.14" + "phpunit/phpunit": "^9.0", + "fakerphp/faker": "1.20.*" } } diff --git a/tools/.psalm/composer.json b/tools/.psalm/composer.json index e2261577..70de5195 100644 --- a/tools/.psalm/composer.json +++ b/tools/.psalm/composer.json @@ -1,5 +1,5 @@ { "require": { - "vimeo/psalm": "^4.22" + "vimeo/psalm": "^5.23" } } diff --git a/tools/.php-coveralls/.gitignore b/tools/.rector/.gitignore similarity index 100% rename from tools/.php-coveralls/.gitignore rename to tools/.rector/.gitignore diff --git a/tools/.rector/composer.json b/tools/.rector/composer.json new file mode 100644 index 00000000..056d015c --- /dev/null +++ b/tools/.rector/composer.json @@ -0,0 +1,5 @@ +{ + "require": { + "rector/rector": "^1.0" + } +} diff --git a/tools/README.md b/tools/README.md index d8dc27e2..3ceb0402 100644 --- a/tools/README.md +++ b/tools/README.md @@ -5,7 +5,7 @@ The dependencies are not pulled via the main composer.json to keep the dependenc ## Structure of the `tools` folder -Each tool has a hidden (dot) folder with it's tool name. For exmaple, PHP-CS-Fixer uses `tools/.php-cs-fixer`: +Each tool has a hidden (dot) folder with its tool name. For example, PHP-CS-Fixer uses `tools/.php-cs-fixer`: ~~~ tools/ diff --git a/tools/bootstrap.php b/tools/bootstrap.php new file mode 100644 index 00000000..5a34c481 --- /dev/null +++ b/tools/bootstrap.php @@ -0,0 +1,13 @@ +mustRun(); + +require __DIR__.'/.phpunit/vendor/autoload.php'; diff --git a/tools/php-coveralls b/tools/phpstan similarity index 53% rename from tools/php-coveralls rename to tools/phpstan index 657546b6..9d0b643e 100755 --- a/tools/php-coveralls +++ b/tools/phpstan @@ -3,5 +3,5 @@ DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")") -composer install --working-dir=$DIR/.php-coveralls --quiet -exec $DIR/.php-coveralls/vendor/bin/php-coveralls "$@" +composer install --working-dir=$DIR/.phpstan --quiet +exec $DIR/.phpstan/vendor/bin/phpstan "$@" diff --git a/tools/rector b/tools/rector new file mode 100755 index 00000000..c5f602ae --- /dev/null +++ b/tools/rector @@ -0,0 +1,7 @@ +#!/bin/bash +# This file was created automatically by cotor as a tool wrapper. + +DIR=$(realpath "$(dirname "${BASH_SOURCE[0]}")") + +composer install --working-dir=$DIR/.rector --quiet +exec $DIR/.rector/vendor/bin/rector "$@"