mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-08 05:43:18 +01:00
Compare commits
2 Commits
feature/do
...
feature/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d1d9a4ee7 | ||
|
|
ba778bdddf |
@@ -60,7 +60,6 @@ abstract class ApiException extends Exception
|
||||
*/
|
||||
public function report(): bool
|
||||
{
|
||||
// TODO: temporary activated
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers extends ApiException
|
||||
{
|
||||
public const string KEY = 'can_not_delete_user_who_is_owner_of_organization_with_multiple_members';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class CanNotRemoveOwnerFromOrganization extends ApiException
|
||||
{
|
||||
public const string KEY = 'can_not_remove_owner_from_organization';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class ChangingRoleOfPlaceholderIsNotAllowed extends ApiException
|
||||
{
|
||||
public const string KEY = 'changing_role_of_placeholder_is_not_allowed';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class ChangingRoleToPlaceholderIsNotAllowed extends ApiException
|
||||
{
|
||||
public const string KEY = 'changing_role_to_placeholder_is_not_allowed';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,16 @@ class EntityStillInUseApiException extends ApiException
|
||||
|
||||
public const string KEY = 'entity_still_in_use';
|
||||
|
||||
/**
|
||||
* Report the exception.
|
||||
*
|
||||
* @return bool true means the exception handler will not report it again
|
||||
*/
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the translated message for the exception.
|
||||
*/
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class FeatureIsNotAvailableInFreePlanApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'feature_is_not_available_in_free_plan';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class InactiveUserCanNotBeUsedApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'inactive_user_can_not_be_used';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class InvitationForTheEmailAlreadyExistsApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'invitation_for_the_email_already_exists';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class OnlyOwnerCanChangeOwnership extends ApiException
|
||||
{
|
||||
public const string KEY = 'only_owner_can_change_ownership';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class OnlyPlaceholdersCanBeMergedIntoAnotherMember extends ApiException
|
||||
{
|
||||
public const string KEY = 'only_placeholders_can_be_merged_into_another_member';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class OrganizationHasNoSubscriptionButMultipleMembersException extends ApiException
|
||||
{
|
||||
public const string KEY = 'organization_has_no_subscription_but_multiple_members';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class OrganizationNeedsAtLeastOneOwner extends ApiException
|
||||
{
|
||||
public const string KEY = 'organization_needs_at_least_one_owner';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class OverlappingTimeEntryApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'overlapping_time_entry';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class ThisPlaceholderCanNotBeInvitedUseTheMergeToolInsteadException extends ApiException
|
||||
{
|
||||
public const string KEY = 'this_placeholder_can_not_be_invited_use_the_merge_tool_instead_api_exception';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class TimeEntryCanNotBeRestartedApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'time_entry_can_not_be_restarted';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,14 @@ namespace App\Exceptions\Api;
|
||||
class TimeEntryStillRunningApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'time_entry_still_running';
|
||||
|
||||
/**
|
||||
* Report the exception.
|
||||
*
|
||||
* @return bool true means the exception handler will not report it again
|
||||
*/
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class UserIsAlreadyMemberOfOrganizationApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'user_is_already_member_of_organization';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class UserIsAlreadyMemberOfProjectApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'user_is_already_member_of_project';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class UserNotPlaceholderApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'user_not_placeholder';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
||||
class UserResendEmailVerificationNoPendingEmailApiException extends ApiException
|
||||
{
|
||||
public const string KEY = 'user_resend_email_verification_no_pending_email';
|
||||
|
||||
public function report(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace App\Exceptions;
|
||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
@@ -34,6 +35,10 @@ class Handler extends ExceptionHandler
|
||||
//
|
||||
});
|
||||
|
||||
$this->dontReportWhen(fn (Throwable $e): bool => $e instanceof OAuthServerException
|
||||
&& $e->getErrorType() === 'access_denied'
|
||||
&& $e->getHttpStatusCode() === 401);
|
||||
|
||||
// A request on an untrusted host (see App\Http\Middleware\TrustHosts)
|
||||
// otherwise renders as a bare "Bad request." 400. Show a message that
|
||||
// says how to fix it instead. The framework has already converted the
|
||||
|
||||
96
tests/Unit/Exceptions/Api/ApiExceptionTest.php
Normal file
96
tests/Unit/Exceptions/Api/ApiExceptionTest.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Exceptions\Api;
|
||||
|
||||
use App\Exceptions\Api\ApiException;
|
||||
use App\Exceptions\Api\CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers;
|
||||
use App\Exceptions\Api\CanNotRemoveOwnerFromOrganization;
|
||||
use App\Exceptions\Api\ChangingRoleOfPlaceholderIsNotAllowed;
|
||||
use App\Exceptions\Api\ChangingRoleToPlaceholderIsNotAllowed;
|
||||
use App\Exceptions\Api\EntityStillInUseApiException;
|
||||
use App\Exceptions\Api\FeatureIsNotAvailableInFreePlanApiException;
|
||||
use App\Exceptions\Api\InactiveUserCanNotBeUsedApiException;
|
||||
use App\Exceptions\Api\InvitationForTheEmailAlreadyExistsApiException;
|
||||
use App\Exceptions\Api\OnlyOwnerCanChangeOwnership;
|
||||
use App\Exceptions\Api\OnlyPlaceholdersCanBeMergedIntoAnotherMember;
|
||||
use App\Exceptions\Api\OrganizationHasNoSubscriptionButMultipleMembersException;
|
||||
use App\Exceptions\Api\OrganizationNeedsAtLeastOneOwner;
|
||||
use App\Exceptions\Api\OverlappingTimeEntryApiException;
|
||||
use App\Exceptions\Api\PdfRendererIsNotConfiguredException;
|
||||
use App\Exceptions\Api\PersonalAccessClientIsNotConfiguredException;
|
||||
use App\Exceptions\Api\ThisPlaceholderCanNotBeInvitedUseTheMergeToolInsteadException;
|
||||
use App\Exceptions\Api\TimeEntryCanNotBeRestartedApiException;
|
||||
use App\Exceptions\Api\TimeEntryStillRunningApiException;
|
||||
use App\Exceptions\Api\UserIsAlreadyMemberOfOrganizationApiException;
|
||||
use App\Exceptions\Api\UserIsAlreadyMemberOfProjectApiException;
|
||||
use App\Exceptions\Api\UserNotPlaceholderApiException;
|
||||
use App\Exceptions\Api\UserResendEmailVerificationNoPendingEmailApiException;
|
||||
use App\Service\Export\ExportException;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ApiExceptionTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @return iterable<string, array{ApiException}>
|
||||
*/
|
||||
public static function expectedApiExceptionProvider(): iterable
|
||||
{
|
||||
yield CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers::class => [new CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers];
|
||||
yield CanNotRemoveOwnerFromOrganization::class => [new CanNotRemoveOwnerFromOrganization];
|
||||
yield ChangingRoleOfPlaceholderIsNotAllowed::class => [new ChangingRoleOfPlaceholderIsNotAllowed];
|
||||
yield ChangingRoleToPlaceholderIsNotAllowed::class => [new ChangingRoleToPlaceholderIsNotAllowed];
|
||||
yield EntityStillInUseApiException::class => [new EntityStillInUseApiException('member', 'time_entry')];
|
||||
yield FeatureIsNotAvailableInFreePlanApiException::class => [new FeatureIsNotAvailableInFreePlanApiException];
|
||||
yield InactiveUserCanNotBeUsedApiException::class => [new InactiveUserCanNotBeUsedApiException];
|
||||
yield InvitationForTheEmailAlreadyExistsApiException::class => [new InvitationForTheEmailAlreadyExistsApiException];
|
||||
yield OnlyOwnerCanChangeOwnership::class => [new OnlyOwnerCanChangeOwnership];
|
||||
yield OnlyPlaceholdersCanBeMergedIntoAnotherMember::class => [new OnlyPlaceholdersCanBeMergedIntoAnotherMember];
|
||||
yield OrganizationHasNoSubscriptionButMultipleMembersException::class => [new OrganizationHasNoSubscriptionButMultipleMembersException];
|
||||
yield OrganizationNeedsAtLeastOneOwner::class => [new OrganizationNeedsAtLeastOneOwner];
|
||||
yield OverlappingTimeEntryApiException::class => [new OverlappingTimeEntryApiException];
|
||||
yield ThisPlaceholderCanNotBeInvitedUseTheMergeToolInsteadException::class => [new ThisPlaceholderCanNotBeInvitedUseTheMergeToolInsteadException];
|
||||
yield TimeEntryCanNotBeRestartedApiException::class => [new TimeEntryCanNotBeRestartedApiException];
|
||||
yield TimeEntryStillRunningApiException::class => [new TimeEntryStillRunningApiException];
|
||||
yield UserIsAlreadyMemberOfOrganizationApiException::class => [new UserIsAlreadyMemberOfOrganizationApiException];
|
||||
yield UserIsAlreadyMemberOfProjectApiException::class => [new UserIsAlreadyMemberOfProjectApiException];
|
||||
yield UserNotPlaceholderApiException::class => [new UserNotPlaceholderApiException];
|
||||
yield UserResendEmailVerificationNoPendingEmailApiException::class => [new UserResendEmailVerificationNoPendingEmailApiException];
|
||||
}
|
||||
|
||||
#[DataProvider('expectedApiExceptionProvider')]
|
||||
public function test_expected_api_exceptions_are_not_reported(ApiException $exception): void
|
||||
{
|
||||
// Arrange: exception supplied by the data provider
|
||||
|
||||
// Act
|
||||
$reportingHandled = $exception->report();
|
||||
|
||||
// Assert
|
||||
$this->assertTrue($reportingHandled);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, array{ApiException}>
|
||||
*/
|
||||
public static function operationalApiExceptionProvider(): iterable
|
||||
{
|
||||
yield PdfRendererIsNotConfiguredException::class => [new PdfRendererIsNotConfiguredException];
|
||||
yield PersonalAccessClientIsNotConfiguredException::class => [new PersonalAccessClientIsNotConfiguredException];
|
||||
yield ExportException::class => [new ExportException];
|
||||
}
|
||||
|
||||
#[DataProvider('operationalApiExceptionProvider')]
|
||||
public function test_operational_api_exceptions_are_reported(ApiException $exception): void
|
||||
{
|
||||
// Arrange: exception supplied by the data provider
|
||||
|
||||
// Act
|
||||
$reportingHandled = $exception->report();
|
||||
|
||||
// Assert
|
||||
$this->assertFalse($reportingHandled);
|
||||
}
|
||||
}
|
||||
40
tests/Unit/Exceptions/HandlerTest.php
Normal file
40
tests/Unit/Exceptions/HandlerTest.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Exceptions;
|
||||
|
||||
use App\Exceptions\Handler;
|
||||
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||
use RuntimeException;
|
||||
use Tests\TestCase;
|
||||
|
||||
class HandlerTest extends TestCase
|
||||
{
|
||||
public function test_oauth_access_denied_exceptions_are_not_reported(): void
|
||||
{
|
||||
// Arrange
|
||||
$exception = OAuthServerException::accessDenied(
|
||||
'Access token could not be verified',
|
||||
previous: new RuntimeException('The token is expired')
|
||||
);
|
||||
|
||||
// Act
|
||||
$shouldReport = app(Handler::class)->shouldReport($exception);
|
||||
|
||||
// Assert
|
||||
$this->assertFalse($shouldReport);
|
||||
}
|
||||
|
||||
public function test_operational_oauth_exceptions_are_reported(): void
|
||||
{
|
||||
// Arrange
|
||||
$exception = OAuthServerException::serverError('Signing key could not be read');
|
||||
|
||||
// Act
|
||||
$shouldReport = app(Handler::class)->shouldReport($exception);
|
||||
|
||||
// Assert
|
||||
$this->assertTrue($shouldReport);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user