mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-10-08 05:43:18 +01:00
Compare commits
2 Commits
feature/po
...
feature/su
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d1d9a4ee7 | ||
|
|
ba778bdddf |
@@ -60,7 +60,6 @@ abstract class ApiException extends Exception
|
|||||||
*/
|
*/
|
||||||
public function report(): bool
|
public function report(): bool
|
||||||
{
|
{
|
||||||
// TODO: temporary activated
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers extends ApiException
|
class CanNotDeleteUserWhoIsOwnerOfOrganizationWithMultipleMembers extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'can_not_delete_user_who_is_owner_of_organization_with_multiple_members';
|
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
|
class CanNotRemoveOwnerFromOrganization extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'can_not_remove_owner_from_organization';
|
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
|
class ChangingRoleOfPlaceholderIsNotAllowed extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'changing_role_of_placeholder_is_not_allowed';
|
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
|
class ChangingRoleToPlaceholderIsNotAllowed extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'changing_role_to_placeholder_is_not_allowed';
|
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';
|
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.
|
* Get the translated message for the exception.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -7,4 +7,9 @@ namespace App\Exceptions\Api;
|
|||||||
class FeatureIsNotAvailableInFreePlanApiException extends ApiException
|
class FeatureIsNotAvailableInFreePlanApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'feature_is_not_available_in_free_plan';
|
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
|
class InactiveUserCanNotBeUsedApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'inactive_user_can_not_be_used';
|
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
|
class InvitationForTheEmailAlreadyExistsApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'invitation_for_the_email_already_exists';
|
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
|
class OnlyOwnerCanChangeOwnership extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'only_owner_can_change_ownership';
|
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
|
class OnlyPlaceholdersCanBeMergedIntoAnotherMember extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'only_placeholders_can_be_merged_into_another_member';
|
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
|
class OrganizationHasNoSubscriptionButMultipleMembersException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'organization_has_no_subscription_but_multiple_members';
|
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
|
class OrganizationNeedsAtLeastOneOwner extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'organization_needs_at_least_one_owner';
|
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
|
class OverlappingTimeEntryApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'overlapping_time_entry';
|
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
|
class ThisPlaceholderCanNotBeInvitedUseTheMergeToolInsteadException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'this_placeholder_can_not_be_invited_use_the_merge_tool_instead_api_exception';
|
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
|
class TimeEntryCanNotBeRestartedApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'time_entry_can_not_be_restarted';
|
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
|
class TimeEntryStillRunningApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'time_entry_still_running';
|
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
|
class UserIsAlreadyMemberOfOrganizationApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'user_is_already_member_of_organization';
|
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
|
class UserIsAlreadyMemberOfProjectApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'user_is_already_member_of_project';
|
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
|
class UserNotPlaceholderApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'user_not_placeholder';
|
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
|
class UserResendEmailVerificationNoPendingEmailApiException extends ApiException
|
||||||
{
|
{
|
||||||
public const string KEY = 'user_resend_email_verification_no_pending_email';
|
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\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
use League\OAuth2\Server\Exception\OAuthServerException;
|
||||||
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
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)
|
// A request on an untrusted host (see App\Http\Middleware\TrustHosts)
|
||||||
// otherwise renders as a bare "Bad request." 400. Show a message that
|
// otherwise renders as a bare "Bad request." 400. Show a message that
|
||||||
// says how to fix it instead. The framework has already converted the
|
// 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