Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests #8

Open
wants to merge 25 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: "Code Coverage"

on:
push:
branches: [ "main" ,"switch-to-s2s-oauth-apps-tests"]
pull_request:
branches: [ "main" ,"switch-to-s2s-oauth-apps-tests"]

jobs:
code-coverage:

runs-on: "ubuntu-latest"

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install PHP with extensions
uses: shivammathur/setup-php@v2
with:
coverage: xdebug
php-version: '8.1'

- name: Install dependencies
run: composer update --ansi --no-interaction --no-progress --optimize-autoloader

- name: Collect code coverage with PHPUnit
run: Packages/Libraries/phpunit/phpunit/phpunit --bootstrap Tests/UnitTestBootstrap.php --configuration Tests/phpunit.xml --colors=always --coverage-clover=.build/logs/clover.xml

- name: Upload coverage reports to Codecov with GitHub Action
uses: codecov/codecov-action@v3
40 changes: 40 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: PHPUnit Tests

on:
push:
branches: [ "main" ,"switch-to-s2s-oauth-apps-tests"]
pull_request:
branches: [ "main" ,"switch-to-s2s-oauth-apps-tests"]

jobs:
run-tests:
name: "Test (PHP ${{ matrix.php-versions }}, Neos ${{ matrix.neos-versions }})"

strategy:
fail-fast: false
matrix:
php-versions: [ '8.1' ]
neos-versions: [ '7.3' ]
include:
- php-versions: '8.1'
neos-versions: '8.3'

runs-on: ubuntu-latest

steps:
- name: "Checkout code"
uses: actions/checkout@v4

- name: "Install PHP with extensions"
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}

- name: Set Neos Version
run: composer require neos/neos ^${{ matrix.neos-versions }} --no-progress --no-interaction

- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist

- name: Execute tests via PHPUnit
run: composer test
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ node_modules
*.DS_Store
/.idea
.remote-sync.json
/bin
/Packages
composer.lock
.phpunit.result.cache
Tests/Reports
12 changes: 12 additions & 0 deletions Classes/Domain/Model/ZoomApiAccessToken.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace CodeQ\ZoomApi\Domain\Model;

class ZoomApiAccessToken
{
public function __construct(
public readonly string $accessToken,
public readonly array $scope
) {
}
}
95 changes: 95 additions & 0 deletions Classes/Domain/Service/ZoomApiAccessTokenFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

namespace CodeQ\ZoomApi\Domain\Service;

use CodeQ\ZoomApi\Domain\Model\ZoomApiAccessToken;
use CodeQ\ZoomApi\ZoomApiException;
use GuzzleHttp\Client;
use Neos\Flow\Annotations as Flow;
use Psr\Log\LoggerInterface;

/**
* @Flow\Scope("singleton")
*/
class ZoomApiAccessTokenFactory
{
private Client $client;

/**
* @Flow\InjectConfiguration
* @var array
*/
protected array $settings;

/**
* @Flow\Inject
* @var LoggerInterface
*/
protected $systemLogger;

/**
* @return void
* @throws ZoomApiException
*/
public function initializeObject(): void
{
$zoomApiClientId = $this->settings['auth']['clientId'];
$zoomApiClientSecret = $this->settings['auth']['clientSecret'];
if (!$zoomApiClientId || !$zoomApiClientSecret) {
throw new ZoomApiException('Please set a Client ID and Secret for CodeQ.ZoomApi to be able to authenticate.', 1695830249149);
}
$this->client = $this->buildClient($zoomApiClientId, $zoomApiClientSecret);
}

/**
* @return ZoomApiAccessToken
* @throws ZoomApiException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function createFromConfiguration(): ZoomApiAccessToken
{
$zoomApiAccountId = $this->settings['auth']['accountId'];
if (!$zoomApiAccountId) {
throw new ZoomApiException('Please set a Zoom Account ID for CodeQ.ZoomApi to be able to authenticate.', 1695904285296);
}
$response = $this->client->post('oauth/token', [
'http_errors' => false,
'form_params' => [
'grant_type' => 'account_credentials',
'account_id' => $zoomApiAccountId
]
]);

if ($response->getStatusCode() !== 200) {
throw new ZoomApiException('Could not fetch Zoom access token. Please check the settings for account ID, client ID and client secret, as well as your Zoom app.', 1695040346621);
}

$responseBodyAsArray = json_decode($response->getBody()->getContents(), true);

if (!str_contains($responseBodyAsArray['scope'], 'user:read:admin') || !str_contains($responseBodyAsArray['scope'], 'recording:read:admin') || !str_contains($responseBodyAsArray['scope'], 'meeting:read:admin')) {
throw new ZoomApiException('Please ensure your Zoom app has the following scopes: user:read:admin, recording:read:admin, meeting:read:admin', 1695040540417);
}

return new ZoomApiAccessToken(
$responseBodyAsArray['access_token'],
explode(',', $responseBodyAsArray['scope'])
);
}

/**
* @param string $zoomApiClientId
* @param string $zoomApiClientSecret
*
* @return Client
*/
protected function buildClient(string $zoomApiClientId, string $zoomApiClientSecret): Client
{
return (new Client([
'base_uri' => 'https://zoom.us/',
'headers' => [
'Authorization' => "Basic " . base64_encode($zoomApiClientId . ':' . $zoomApiClientSecret),
'Content-Type' => 'application/json',
],
]));
}
}
114 changes: 35 additions & 79 deletions Classes/Domain/Service/ZoomApiService.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<?php

declare(strict_types=1);

namespace CodeQ\ZoomApi\Domain\Service;

use CodeQ\ZoomApi\Utility\TimeUtility;
use DateInterval;
use DateTime;
use DateTimeImmutable;
Expand All @@ -24,10 +27,10 @@ class ZoomApiService
private Client $client;

/**
* @Flow\InjectConfiguration
* @var array
* @Flow\Inject
* @var ZoomApiAccessTokenFactory
*/
protected array $settings;
protected $accessTokenFactory;

/**
* @var VariableFrontend
Expand All @@ -45,16 +48,13 @@ class ZoomApiService
*/
public function initializeObject(): void
{
$zoomApiAccountId = $this->settings['auth']['accountId'];
$zoomApiClientId = $this->settings['auth']['clientId'];
$zoomApiClientSecret = $this->settings['auth']['clientSecret'];
if (!$zoomApiAccountId || !$zoomApiClientId || !$zoomApiClientSecret) {
throw new ZoomApiException('Please set a Zoom Account ID, Client ID and Secret for CodeQ.ZoomApi to be able to authenticate.');
}

$accessToken = $this->getAccessToken($zoomApiAccountId, $zoomApiClientId, $zoomApiClientSecret);
$accessToken = $this->accessTokenFactory->createFromConfiguration();
$this->client = $this->buildClient($accessToken->accessToken);
}

$this->client = (new Client([
protected function buildClient(string $accessToken): Client
{
return (new Client([
'base_uri' => 'https://api.zoom.us/v2/',
'headers' => [
'Authorization' => "Bearer $accessToken",
Expand All @@ -75,9 +75,9 @@ public function getUpcomingMeetings(bool $skipCache = false): array
{
$cacheEntryIdentifier = 'upcomingMeetings';

$upcomingMeetings = $this->getCacheEntry($cacheEntryIdentifier);
if (!$skipCache && $upcomingMeetings !== false) {
return $upcomingMeetings;
$cachedUpcomingMeetings = $this->getCacheEntry($cacheEntryIdentifier);
if (!$skipCache && $cachedUpcomingMeetings !== false) {
return $cachedUpcomingMeetings;
}

$upcomingMeetings = $this->fetchData(
Expand Down Expand Up @@ -110,29 +110,19 @@ public function getUpcomingMeetings(bool $skipCache = false): array
*/
public function getRecordings(DateTime|string $from, DateTime|string $to, bool $skipCache = false): array
{
$cacheEntryIdentifier = sprintf('recordings_%s_%s', is_string($from) ? $from : $from->format('Y-m-d'), is_string($to) ? $to : $to->format('Y-m-d'));

$recordings = $this->getCacheEntry($cacheEntryIdentifier);
if (!$skipCache && $recordings !== false) {
return $recordings;
}

if (is_string($from)) {
$from = new DateTimeImmutable($from);
} else {
$from = DateTimeImmutable::createFromMutable($from);
}

if (is_string($to)) {
$to = new DateTimeImmutable($to);
} else {
$to = DateTimeImmutable::createFromMutable($to);
}
$from = TimeUtility::convertStringOrDateTimeToDateTimeImmutable($from);
$to = TimeUtility::convertStringOrDateTimeToDateTimeImmutable($to);

if ($from > $to) {
throw new InvalidArgumentException('The from date must be after the to date');
}

$cacheEntryIdentifier = sprintf('recordings_%s_%s', $from->format('Y-m-d'), $to->format('Y-m-d'));
$cachedRecordings = $this->getCacheEntry($cacheEntryIdentifier);
if (!$skipCache && $cachedRecordings !== false) {
return $cachedRecordings;
}

$recordings = $this->fetchDataForDateRange($from, $to);

try {
Expand Down Expand Up @@ -166,7 +156,7 @@ private function fetchDataForDateRange(DateTimeImmutable $from, DateTimeImmutabl

// The zoom API only returns up to one month per request, so if the date range between $from and $to is
// bigger than one month we chunk our requests. We start by our $to date and subtract one month from it.
if ($this->dateDifferenceIsBiggerThanOneMonth($from, $to)) {
if (TimeUtility::dateDifferenceIsBiggerThanOneMonth($from, $to)) {
$from = $to->sub(DateInterval::createFromDateString('1 month'));
$getMoreMonths = true;
}
Expand Down Expand Up @@ -211,8 +201,10 @@ private function fetchData($uri, string $paginatedDataKey): array

if (!array_key_exists($paginatedDataKey, $responseData)) {
throw new ZoomApiException("Could not find key $paginatedDataKey. Response data: "
. print_r($aggregatedData,
true));
. print_r(
$aggregatedData,
true
));
}

$aggregatedData = array_merge($aggregatedData, $responseData[$paginatedDataKey]);
Expand All @@ -231,56 +223,20 @@ private function fetchData($uri, string $paginatedDataKey): array
*/
private function fetchPaginatedData(string $uri, string $paginatedDataKey): array
{
$response = $this->client->get($uri);
$response = $this->client->get($uri, [
'http_errors' => false
]);
if ($response->getStatusCode() !== 200) {
throw new ZoomApiException(sprintf('Could not fetch Zoom paginated data for data key "%s", returned status "%s"', $paginatedDataKey, $response->getStatusCode()), 1695239983421);
Copy link
Contributor

@krsriq krsriq Oct 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is still suspiciously not covered by tests - and has a bug (guess you can find it on your own ;) ).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sollte jetzt gefixt sein, was meinst du? ;-)

Ich habe jetzt auch noch PHPStan hinzugefügt und Codesniffer

}
$bodyContents = $response->getBody()->getContents();
return json_decode($bodyContents, true);
}

private function dateDifferenceIsBiggerThanOneMonth(DateTimeImmutable $from, DateTimeImmutable $to): bool
{
$dateDifference = $from->diff($to);
$differenceInMonths = $dateDifference->y * 12 + $dateDifference->m;
return $differenceInMonths > 0;
}

/**
* @param string $accountId
* @param string $zoomApiClientId
* @param string $zoomApiClientSecret
*
* @return string|null
* @throws GuzzleException|ZoomApiException
*/
private function getAccessToken(string $accountId, string $zoomApiClientId, string $zoomApiClientSecret): ?string
{
$client = new Client([
'base_uri' => 'https://zoom.us/',
'headers' => [
'Authorization' => "Basic " . base64_encode($zoomApiClientId . ':' . $zoomApiClientSecret),
'Content-Type' => 'application/json',
],
]);
$response = $client->post('oauth/token', [
'form_params' => [
'grant_type' => 'account_credentials',
'account_id' => $accountId
]
]);

if ($response->getStatusCode() !== 200) {
throw new ZoomApiException('Could not fetch Zoom access token. Please check the settings for account ID, client ID and client secret, as well as your Zoom app.', 1695040346621);
}

$responseBodyAsArray = json_decode($response->getBody()->getContents(), true);
$bodyContentsArray = json_decode($bodyContents, true);

if (!str_contains($responseBodyAsArray['scope'], 'user:read:admin') || !str_contains($responseBodyAsArray['scope'], 'recording:read:admin') || !str_contains($responseBodyAsArray['scope'], 'meeting:read:admin')) {
throw new ZoomApiException('Please ensure your Zoom app has the following scopes: user:read:admin, recording:read:admin, meeting:read:admin', 1695040540417);
if ($bodyContentsArray === null || !array_key_exists($paginatedDataKey, $bodyContentsArray)) {
throw new ZoomApiException(sprintf('Could not fetch Zoom paginated data for data key "%s", returned empty response', $paginatedDataKey), 1695828849253);
}

return $responseBodyAsArray['access_token'];
return $bodyContentsArray;
}

/**
Expand Down
Loading