Skip to content

Commit 0764d2e

Browse files
[SDK-2752] Expand test coverage and finish migration to Pest framework (#552)
* tests: Expand test coverage * tests: Expand test coverage * tests: Expand test coverage * tests: Fix unit tests running in Windows * tests: Expand test coverage * fix: handleInvitation() should return a URL * fix: Improve ConfigurableMixin type validation * tests: Code style pass * tests: Address test warning about dynamic class names * tests: Expand test coverage * Fix merge conflicts * tests: Fix test warning; PHP < 8.0 doesn't support `mixed` as a typehint * change: Rename InMemoryStore to MemoryStore to follow naming convention of other storage mediums. * tests: Update Psr6StoreTest to PEST format. * tests: Expand test coverage * tests: Improvements to toolkit::every() tests * tests: Fix lack of splat operator support for assoc arrays in PHP < 8.0 * tests: Updates for PHP < 8.0 warnings * tests: Expand test coverage * tests: Expand test coverage * tests: Transition to PEST expectations * tests: Expand test coverage * tests: Update test coverage * Add Pest dependencies * Adopt expectation API * Shift cleanup * tests: Adopt Pest-style exception expectations syntax * tests: Expand test coverage * tests: Expand test coverage * Add `hyperf/event` for PSR-4 unit testing * tests: Expand test coverage * tests: Resolve phpstan warnings Co-authored-by: Shift <[email protected]>
1 parent b46fa2f commit 0764d2e

File tree

78 files changed

+5526
-4793
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+5526
-4793
lines changed

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,19 @@
4343
"psr/http-message-implementation": "^1.0"
4444
},
4545
"require-dev": {
46-
"symfony/cache": "^4.4 || ^5.2",
4746
"ergebnis/phpstan-rules": "^0.15",
4847
"firebase/php-jwt": "^5.0",
48+
"hyperf/event": "^2.2",
4949
"infection/infection": "^0.23",
5050
"mockery/mockery": "^1.4",
5151
"nunomaduro/phpinsights": "^2.0",
5252
"nyholm/psr7": "^1.4",
5353
"pestphp/pest": "^1.0",
54+
"pestphp/pest-plugin-parallel": "^0.2.0",
5455
"php-http/mock-client": "^1.4",
5556
"phpstan/phpstan": "^0.12",
5657
"phpstan/phpstan-strict-rules": "^0.12",
58+
"symfony/cache": "^4.4 || ^5.2",
5759
"thecodingmachine/phpstan-strict-rules": "^0.12",
5860
"vimeo/psalm": "^4.7"
5961
},

phpinsights.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
'src/Configuration/SdkConfiguration.php',
5151
'src/Configuration/SdkState.php',
5252
'src/Store/SessionStore.php',
53-
'src/Store/InMemoryStorage.php',
53+
'src/Store/MemoryStore.php',
5454
'src/Store/Psr6Store.php',
5555
],
5656
],

src/API/Management/ManagementEndpoint.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,14 @@ final public function getHttpClient(): HttpClient
4343
*/
4444
final public function getLastRequest(): ?HttpRequest
4545
{
46-
return $this->httpClient->getLastRequest();
46+
return $this->getHttpClient()->getLastRequest();
4747
}
4848

4949
/**
5050
* Return a ResponsePaginator instance configured for the last HttpRequest.
5151
*/
5252
final public function getResponsePaginator(): HttpResponsePaginator
5353
{
54-
return new HttpResponsePaginator($this->httpClient);
54+
return new HttpResponsePaginator($this->getHttpClient());
5555
}
5656
}

src/API/Management/Tenants.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ final class Tenants extends ManagementEndpoint
2626
*
2727
* @link https://auth0.com/docs/api/management/v2#!/Tenants/tenant_settings_route
2828
*/
29-
public function get(
29+
public function getSettings(
3030
?RequestOptions $options = null
3131
): ResponseInterface {
3232
return $this->getHttpClient()
@@ -48,15 +48,15 @@ public function get(
4848
*
4949
* @link https://auth0.com/docs/api/management/v2#!/Tenants/patch_settings
5050
*/
51-
public function update(
51+
public function updateSettings(
5252
array $body,
5353
?RequestOptions $options = null
5454
): ResponseInterface {
55-
[$body] = Toolkit::filter([$body])->string()->trim();
55+
[$body] = Toolkit::filter([$body])->array()->trim();
5656

5757
Toolkit::assert([
5858
[$body, \Auth0\SDK\Exception\ArgumentException::missing('body')],
59-
])->isString();
59+
])->isArray();
6060

6161
return $this->getHttpClient()
6262
->method('patch')

src/Auth0.php

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,34 @@ public function signup(
151151
], $params));
152152
}
153153

154+
/**
155+
* If invitation parameters are present in the request, handle extraction and return a URL for redirection to Universal Login to accept. Returns null if no invitation parameters were found.
156+
*
157+
* @param string|null $redirectUrl Optional. URI to return to after logging out. Defaults to the SDK's configured redirectUri.
158+
* @param array<int|string|null>|null $params Additional parameters to include with the request.
159+
*
160+
* @throws \Auth0\SDK\Exception\ConfigurationException When a Client ID is not configured.
161+
* @throws \Auth0\SDK\Exception\ConfigurationException When `redirectUri` is not specified, and supplied SdkConfiguration does not have a default redirectUri configured.
162+
*
163+
* @link https://auth0.com/docs/universal-login/new-experience
164+
* @link https://auth0.com/docs/api/authentication#login
165+
*/
166+
public function handleInvitation(
167+
?string $redirectUrl = null,
168+
?array $params = null
169+
): ?string {
170+
$invite = $this->getInvitationParameters();
171+
172+
if ($invite !== null) {
173+
return $this->login($redirectUrl, Toolkit::merge([
174+
'invitation' => (string) $invite->invitation,
175+
'organization' => (string) $invite->organization,
176+
], $params));
177+
}
178+
179+
return null;
180+
}
181+
154182
/**
155183
* Delete any persistent data and clear out all stored properties, and return the URI to Auth0 /logout endpoint for redirection.
156184
*
@@ -173,16 +201,19 @@ public function logout(
173201

174202
/**
175203
* Delete any persistent data and clear out all stored properties.
204+
*
205+
* @oaram bool $transient When true, data in transient storage is also cleared.
176206
*/
177-
public function clear(): self
178-
{
207+
public function clear(
208+
bool $transient = true
209+
): self {
179210
// Delete all data in the session storage medium.
180211
if ($this->configuration()->hasSessionStorage()) {
181212
$this->configuration->getSessionStorage()->purge();
182213
}
183214

184215
// Delete all data in the transient storage medium.
185-
if ($this->configuration()->hasTransientStorage()) {
216+
if ($this->configuration()->hasTransientStorage() && $transient === true) {
186217
$this->configuration->getTransientStorage()->purge();
187218
}
188219

@@ -273,6 +304,8 @@ public function exchange(
273304
return false;
274305
}
275306

307+
$this->clear(false);
308+
276309
if ($state === null || ! $this->getTransientStore()->verify('state', $state)) {
277310
$this->clear();
278311
throw \Auth0\SDK\Exception\StateException::invalidState();
@@ -287,10 +320,6 @@ public function exchange(
287320
}
288321
}
289322

290-
if ($this->getState()->hasUser()) {
291-
$this->clear();
292-
}
293-
294323
$response = $this->authentication()->codeExchange($code, $redirectUri, $codeVerifier);
295324

296325
if (! HttpResponse::wasSuccessful($response)) {
@@ -622,18 +651,20 @@ public function setAccessTokenExpiration(
622651
* Get the specified parameter from POST or GET, depending on configured response mode.
623652
*
624653
* @param string $parameterName Name of the parameter to pull from the request.
654+
* @param int $filter Defaults to FILTER_SANITIZE_STRING. The type of PHP filter_var() filter to apply.
625655
*/
626656
public function getRequestParameter(
627-
string $parameterName
657+
string $parameterName,
658+
int $filter = FILTER_SANITIZE_STRING
628659
): ?string {
629660
$responseMode = $this->configuration()->getResponseMode();
630661

631662
if ($responseMode === 'query' && isset($_GET[$parameterName])) {
632-
return filter_var($_GET[$parameterName], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE);
663+
return filter_var($_GET[$parameterName], $filter, FILTER_NULL_ON_FAILURE);
633664
}
634665

635666
if ($responseMode === 'form_post' && isset($_POST[$parameterName])) {
636-
return filter_var($_POST[$parameterName], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE);
667+
return filter_var($_POST[$parameterName], $filter, FILTER_NULL_ON_FAILURE);
637668
}
638669

639670
return null;
@@ -659,23 +690,6 @@ public function getInvitationParameters(): ?object
659690
return null;
660691
}
661692

662-
/**
663-
* If invitation parameters are present in the request, handle extraction and automatically redirect to Universal Login.
664-
*/
665-
public function handleInvitation(): self
666-
{
667-
$invite = $this->getInvitationParameters();
668-
669-
if ($invite !== null) {
670-
$this->login(null, [
671-
'invitation' => (string) $invite->invitation,
672-
'organization' => (string) $invite->organization,
673-
]);
674-
}
675-
676-
return $this;
677-
}
678-
679693
/**
680694
* Create a transient storage handler using the configured transientStorage medium.
681695
*/

src/Configuration/SdkConfiguration.php

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,8 @@ private function setupStateCookies(): void
353353
* Setup SDK factories.
354354
*
355355
* @throws NotFoundException When a PSR-18 or PSR-17 are not configured, and cannot be discovered.
356+
*
357+
* @codeCoverageIgnore
356358
*/
357359
private function setupStateFactories(): void
358360
{
@@ -450,20 +452,6 @@ private function onStateChange(
450452
throw \Auth0\SDK\Exception\ConfigurationException::invalidAlgorithm();
451453
}
452454

453-
if ($propertyName === 'tokenMaxAge' || $propertyName === 'tokenLeeway') {
454-
if (is_int($propertyValue)) {
455-
// Value was passed as an int, perfect.
456-
return $propertyValue;
457-
}
458-
459-
if (is_numeric($propertyValue)) {
460-
// Value was passed as a string, but it is numeric so cast to int.
461-
return (int) $propertyValue;
462-
}
463-
464-
throw \Auth0\SDK\Exception\ConfigurationException::validationFailed($propertyName);
465-
}
466-
467455
if (in_array($propertyName, ['organization', 'audience'], true)) {
468456
if (is_array($propertyValue) && count($propertyValue) !== 0) {
469457
return $propertyValue;
@@ -475,19 +463,6 @@ private function onStateChange(
475463
return $propertyValue;
476464
}
477465

478-
/**
479-
* Fires when a validation event fails to pass, such as a bad parameter type being used.
480-
*
481-
* @param string $parameter The name of the parameter that failed validation.
482-
*
483-
* @throws ConfigurationException When invoked.
484-
*/
485-
private function onValidationException(
486-
string $parameter
487-
): void {
488-
throw \Auth0\SDK\Exception\ConfigurationException::validationFailed($parameter);
489-
}
490-
491466
/**
492467
* Setup SDK validators based on strategy type.
493468
*/

src/Mixins/ConfigurableMixin.php

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,23 +69,24 @@ public function __call(
6969
$arguments[0] = [ $arguments[0] ];
7070
}
7171

72-
[$arguments] = Toolkit::filter([$arguments])->array()->trim();
72+
$arguments = array_filter(
73+
Toolkit::filter($arguments)->array()->trim(),
74+
static function ($val): bool {
75+
return $val !== null && count($val) !== 0;
76+
}
77+
);
7378

7479
if (count($arguments) !== 0) {
7580
if (is_array($this->configuredState[$propertyName]->value)) {
7681
$this->changeState($propertyName, array_merge($this->configuredState[$propertyName]->value, $arguments[0]));
7782
return $this;
7883
}
7984

80-
if ($this->configuredState[$propertyName]->value === null) {
81-
$this->changeState($propertyName, $arguments[0]);
82-
return $this;
83-
}
84-
8585
$this->changeState($propertyName, $arguments[0]);
86+
return $this;
8687
}
8788

88-
return $this->configuredState[$propertyName]->value;
89+
return $this;
8990
}
9091

9192
throw \Auth0\SDK\Exception\ConfigurationException::getMissing($propertyName);
@@ -101,7 +102,7 @@ public function __call(
101102
throw \Auth0\SDK\Exception\ConfigurationException::getMissing($propertyName);
102103
}
103104

104-
return;
105+
throw \Auth0\SDK\Exception\ArgumentException::unknownMethod($functionName);
105106
}
106107

107108
/**
@@ -148,7 +149,7 @@ private function setState(
148149
// TODO: Replace get_class() w/ ::class when 7.x support is dropped.
149150

150151
// phpcs:ignore
151-
$constructor = new \ReflectionMethod(get_class($this) . '::__construct');
152+
$constructor = new \ReflectionMethod(get_class($this), '__construct');
152153
$parameters = $constructor->getParameters();
153154
$arguments = $args[0];
154155
$usingArgumentsArray = false;
@@ -226,9 +227,6 @@ private function changeState(
226227
string $propertyName,
227228
$propertyValue
228229
): void {
229-
$propertyType = gettype($propertyValue);
230-
$expectedType = $this->configuredState[$propertyName]->type;
231-
232230
if ($this->configurationImmutable) {
233231
throw \Auth0\SDK\Exception\ConfigurationException::setImmutable();
234232
}
@@ -237,18 +235,29 @@ private function changeState(
237235
throw \Auth0\SDK\Exception\ConfigurationException::setMissing($propertyName);
238236
}
239237

240-
if ($propertyType !== $expectedType) {
241-
if (! ($propertyType === 'boolean' && $expectedType === 'bool') &&
242-
! ($propertyType === 'integer' && $expectedType === 'int')) {
243-
if ($propertyValue === null && ! $this->configuredState[$propertyName]->allowsNull) {
244-
throw \Auth0\SDK\Exception\ConfigurationException::setIncompatible($propertyName, $expectedType, $propertyType);
245-
}
238+
$propertyType = gettype($propertyValue);
246239

247-
if ($propertyValue !== null && $this->configuredState[$propertyName]->allowsNull) {
248-
if (! is_object($propertyValue) || ! ($propertyValue instanceof $expectedType)) {
249-
throw \Auth0\SDK\Exception\ConfigurationException::setIncompatibleNullable($propertyName, $expectedType, $propertyType);
250-
}
240+
$normalizedPropertyTypes = [
241+
'boolean' => 'bool',
242+
'integer' => 'int',
243+
];
244+
245+
$propertyType = $normalizedPropertyTypes[$propertyType] ?? $propertyType;
246+
247+
$allowedTypes = [$this->configuredState[$propertyName]->type];
248+
$expectedType = $this->configuredState[$propertyName]->type;
249+
250+
if ($this->configuredState[$propertyName]->allowsNull) {
251+
$allowedTypes[] = 'NULL';
252+
}
253+
254+
if (! in_array($propertyType, $allowedTypes, true)) {
255+
if (! $propertyValue instanceof $expectedType) {
256+
if ($this->configuredState[$propertyName]->allowsNull) {
257+
throw \Auth0\SDK\Exception\ConfigurationException::setIncompatibleNullable($propertyName, $expectedType, $propertyType);
251258
}
259+
260+
throw \Auth0\SDK\Exception\ConfigurationException::setIncompatible($propertyName, $expectedType, $propertyType);
252261
}
253262
}
254263

0 commit comments

Comments
 (0)