diff --git a/.gitignore b/.gitignore index bb2fcf3..1692225 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ .env.backup .env.production .phpunit.result.cache +/code-coverage/ Homestead.json Homestead.yaml auth.json @@ -19,3 +20,5 @@ yarn-error.log supervisord.log supervisord.pid + +*/**/Dummy*.php diff --git a/app/Core/Date/DatetimeFormat.php b/app/Core/Date/DatetimeFormat.php new file mode 100644 index 0000000..ee502e2 --- /dev/null +++ b/app/Core/Date/DatetimeFormat.php @@ -0,0 +1,8 @@ +exceptionMessage = new ExceptionMessageStandard( + 'Something Wrong on Our Server', + ExceptionErrorCode::GENERIC->value + ); + } + + public function getJsonResponse(): Collection + { + return $this->exceptionMessage->getJsonResponse(); + } + + public function getMessage(): string + { + return $this->exceptionMessage->getMessage(); + } +} diff --git a/app/Core/Formatter/ExceptionMessage/ExceptionMessageStandard.php b/app/Core/Formatter/ExceptionMessage/ExceptionMessageStandard.php new file mode 100644 index 0000000..9d6e146 --- /dev/null +++ b/app/Core/Formatter/ExceptionMessage/ExceptionMessageStandard.php @@ -0,0 +1,29 @@ + $this->errorMessage, + 'errorCode' => $this->errorCode, + 'meta' => $this->meta, + ]); + } + + public function getMessage(): string + { + return $this->errorMessage; + } +} diff --git a/app/Core/Formatter/Randomizer/Randomizer.php b/app/Core/Formatter/Randomizer/Randomizer.php new file mode 100644 index 0000000..1b21314 --- /dev/null +++ b/app/Core/Formatter/Randomizer/Randomizer.php @@ -0,0 +1,8 @@ +meta = $meta; + } + + public function getMessage(): string + { + return json_encode([ + 'endpoint' => $this->endpoint, + 'request-id' => $this->requestID, + 'processing-status' => $this->processingStatus->value, + 'message' => $this->message, + 'meta' => $this->meta, + ]); + } +} diff --git a/app/Core/Logger/MessageFormatter/ProcessingStatus.php b/app/Core/Logger/MessageFormatter/ProcessingStatus.php new file mode 100644 index 0000000..f437ab6 --- /dev/null +++ b/app/Core/Logger/MessageFormatter/ProcessingStatus.php @@ -0,0 +1,11 @@ +where('email', $request->getEmail()) + ->exists(); + if ($isEmailExist) { + throw new UserEmailDuplicatedException(new ExceptionMessageStandard( + 'Email is duplicated', + UserExceptionCode::DUPLICATED->value, + )); + } + + $user = new User; + $user->name = $request->getName(); + $user->email = $request->getEmail(); + $user->password = Hash::make($request->getUserPassword()); + $user->save(); + + DB::commit(); + + return $user; + } catch (\Exception $e) { + DB::rollBack(); + throw $e; + } + } + + public function delete(DeleteUserPort $request): void + { + $user = $request->getUserModel(); + $user->delete(); + } + + public function get(GetUserPort $request): User + { + return $request->getUserModel(); + } + + public function getAll(GetAllUserPort $request): LengthAwarePaginator + { + $page = $request->getPage() ?? 1; + $perPage = $request->getPerPage() ?? 30; + $orderDirection = $request->getOrderDirection() ?? OrderDirection::DESCENDING; + $orderBy = $request->getOrderBy() ?? UserOrderBy::CREATED_AT; + + return User::query() + ->orderBy($orderBy->value, $orderDirection->value) + ->paginate($perPage, ['*'], 'page', $page); + } + + public function update(UpdateUserPort $request): User + { + try { + DB::beginTransaction(); + + $user = $request->getUserModel(); + $email = $request->getEmail(); + + $isEmailExist = User::query() + ->where('email', $email) + ->where('id', '<>', $user->id) + ->exists(); + if ($isEmailExist) { + throw new UserEmailDuplicatedException(new ExceptionMessageStandard( + 'Email is already in used', + UserExceptionCode::DUPLICATED->value, + )); + } + + $user->name = $request->getName(); + $user->email = $email; + $user->password = Hash::make($request->getUserPassword()); + $user->save(); + + DB::commit(); + + return $user; + } catch (\Exception $e) { + DB::rollBack(); + throw $e; + } + } +} diff --git a/app/Core/User/UserCoreContract.php b/app/Core/User/UserCoreContract.php new file mode 100644 index 0000000..9b1da9a --- /dev/null +++ b/app/Core/User/UserCoreContract.php @@ -0,0 +1,20 @@ +exceptionMessage->getJsonResponse()->toJson(), + 0, + $previousException + ); + } +} diff --git a/app/Exceptions/Core/User/UserEmailDuplicatedException.php b/app/Exceptions/Core/User/UserEmailDuplicatedException.php new file mode 100644 index 0000000..f7be1d7 --- /dev/null +++ b/app/Exceptions/Core/User/UserEmailDuplicatedException.php @@ -0,0 +1,9 @@ +reportable(function (Throwable $e) { - // - }); + $this->reportable(fn (AbstractHttpException $e) => false); } } diff --git a/app/Exceptions/Http/AbstractHttpException.php b/app/Exceptions/Http/AbstractHttpException.php new file mode 100644 index 0000000..83b88eb --- /dev/null +++ b/app/Exceptions/Http/AbstractHttpException.php @@ -0,0 +1,33 @@ +exceptionMessage->getMessage(), + $this->getStatusCode(), + $previousException + ); + } + + public function render($request) + { + return response()->json( + [ + 'errors' => $this->exceptionMessage->getJsonResponse() + ], + $this->getStatusCode() + ); + } + + abstract protected function getStatusCode(): int; +} diff --git a/app/Exceptions/Http/ConflictException.php b/app/Exceptions/Http/ConflictException.php new file mode 100644 index 0000000..7668fab --- /dev/null +++ b/app/Exceptions/Http/ConflictException.php @@ -0,0 +1,13 @@ +loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::BEGIN, + 'Delete user endpoint', + [ + 'input' => $request->toArray(), + ], + )->getMessage() + ); + + $this->core->delete($request); + + Log::info( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::SUCCESS, + 'Delete user endpoint', + [], + )->getMessage() + ); + + return response()->json([], Response::HTTP_NO_CONTENT); + } catch (Exception $e) { + Log::error( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::ERROR, + $e->getMessage(), + [ + 'trace' => $e->getTrace(), + ], + )->getMessage() + ); + throw new InternalServerErrorException(new ExceptionMessageGeneric); + } + } + + public function index(GetAllUserRequest $request) + { + try { + Log::info( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::BEGIN, + 'Get all user endpoint', + [ + 'input' => $request->toArray(), + ], + )->getMessage() + ); + + $users = $this->core->getAll($request); + + Log::info( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::SUCCESS, + 'Get all user endpoint', + [], + )->getMessage() + ); + + return UserResource::collection($users); + } catch (Exception $e) { + Log::error( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::ERROR, + $e->getMessage(), + [ + 'trace' => $e->getTrace(), + ], + )->getMessage() + ); + throw new InternalServerErrorException(new ExceptionMessageGeneric); + } + } + + public function show(GetUserRequest $request) + { + try { + Log::info( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::BEGIN, + 'Show user endpoint', + [ + 'input' => $request->toArray(), + ], + )->getMessage() + ); + + $user = $this->core->get($request); + + Log::info( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::SUCCESS, + 'Show user endpoint', + [], + )->getMessage() + ); + + return UserResource::make($user) + ->response() + ->setStatusCode(Response::HTTP_OK); + } catch (Exception $e) { + Log::error( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::ERROR, + $e->getMessage(), + [ + 'trace' => $e->getTrace(), + ], + )->getMessage() + ); + throw new InternalServerErrorException(new ExceptionMessageGeneric); + } + } + + public function store(CreateUserRequest $request) + { + try { + Log::info( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::BEGIN, + 'Create user endpoint', + [ + 'input' => $request->toArray(), + ], + )->getMessage() + ); + + $user = $this->core->create($request); + + Log::info( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::SUCCESS, + 'Create user endpoint', + [], + )->getMessage() + ); + + return UserResource::make($user) + ->response() + ->setStatusCode(Response::HTTP_CREATED); + } catch (UserEmailDuplicatedException $e) { + Log::error( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::ERROR, + $e->getMessage(), + [ + 'trace' => $e->getTrace(), + ], + )->getMessage() + ); + throw new ConflictException($e->exceptionMessage); + } catch (Exception $e) { + Log::error( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::ERROR, + $e->getMessage(), + [ + 'trace' => $e->getTrace(), + ], + )->getMessage() + ); + throw new InternalServerErrorException(new ExceptionMessageGeneric); + } + } + + public function update(UpdateUserRequest $request) + { + try { + Log::info( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::BEGIN, + 'Update user endpoint', + [ + 'input' => $request->toArray(), + ], + )->getMessage() + ); + + $user = $this->core->update($request); + + Log::info( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::SUCCESS, + 'Update user endpoint', + [], + )->getMessage() + ); + + return UserResource::make($user); + } catch (UserEmailDuplicatedException $e) { + Log::error( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::ERROR, + $e->getMessage(), + [ + 'trace' => $e->getTrace(), + ], + )->getMessage() + ); + throw new ConflictException($e->exceptionMessage); + } catch (Exception $e) { + Log::error( + $this->loggerFormatter->makeGeneric( + $request->getEndpointInfo(), + $request->getXRequestID(), + ProcessingStatus::ERROR, + $e->getMessage(), + [ + 'trace' => $e->getTrace(), + ], + )->getMessage() + ); + throw new InternalServerErrorException(new ExceptionMessageGeneric); + } + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index d629750..2509966 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -42,6 +42,11 @@ class Kernel extends HttpKernel // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, + \App\Http\Middleware\XRequestIDMiddleware::class, + ], + + 'model-binding' => [ + \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ]; diff --git a/app/Http/Middleware/XRequestIDMiddleware.php b/app/Http/Middleware/XRequestIDMiddleware.php new file mode 100644 index 0000000..a698470 --- /dev/null +++ b/app/Http/Middleware/XRequestIDMiddleware.php @@ -0,0 +1,33 @@ +randomizer->getRandomizeString(); + $request->headers->set(self::HEADER_NAME, $requestID); + + $response = $next($request); + $response->headers->set(self::HEADER_NAME, $requestID); + + return $response; + } +} diff --git a/app/Http/Requests/BaseRequest.php b/app/Http/Requests/BaseRequest.php new file mode 100644 index 0000000..9902961 --- /dev/null +++ b/app/Http/Requests/BaseRequest.php @@ -0,0 +1,65 @@ + + */ + abstract public function rules(): array; + + public function getEndpointInfo(): string + { + return $this->method() . ' ' . $this->url(); + } + + public function getXRequestID(): string + { + return $this->header(XRequestIDMiddleware::HEADER_NAME, ''); + } + + protected function failedValidation(Validator $validator) + { + throw new UnprocessableEntityException(new ExceptionMessageStandard( + 'Structure body/param might be invalid.', + ExceptionErrorCode::INVALID_VALIDATION->value, + $validator->errors()->jsonSerialize(), + )); + } + + protected function failedAuthorization() + { + throw new UnauthorizedException(new ExceptionMessageStandard( + 'Authorization is required to access this resource.', + ExceptionErrorCode::REQUIRE_AUTHORIZATION->value, + )); + } + + protected function getLoggedInUserInstance(): User + { + $user = auth()->user(); + + if (is_null($user)) { + return new User; + } + + return $user; + } +} diff --git a/app/Http/Requests/User/CreateUserRequest.php b/app/Http/Requests/User/CreateUserRequest.php new file mode 100644 index 0000000..31e326f --- /dev/null +++ b/app/Http/Requests/User/CreateUserRequest.php @@ -0,0 +1,46 @@ + + */ + public function rules(): array + { + return [ + 'email' => ['required', 'email', 'max:250'], + 'name' => ['required', 'string', 'max:250'], + 'password' => ['required', 'string'], + ]; + } + + public function getName(): string + { + return $this->input('name'); + } + + public function getEmail(): string + { + return $this->input('email'); + } + + public function getUserPassword(): string + { + return $this->input('password'); + } +} diff --git a/app/Http/Requests/User/DeleteUserRequest.php b/app/Http/Requests/User/DeleteUserRequest.php new file mode 100644 index 0000000..9d7e779 --- /dev/null +++ b/app/Http/Requests/User/DeleteUserRequest.php @@ -0,0 +1,33 @@ + + */ + public function rules(): array + { + return []; + } + + public function getUserModel(): User + { + return $this->route('userID'); + } +} diff --git a/app/Http/Requests/User/GetAllUserRequest.php b/app/Http/Requests/User/GetAllUserRequest.php new file mode 100644 index 0000000..319b59d --- /dev/null +++ b/app/Http/Requests/User/GetAllUserRequest.php @@ -0,0 +1,60 @@ + + */ + public function rules(): array + { + return [ + 'orderBy' => ['bail', 'string', new BackedEnumRule(UserOrderBy::NAME)], + 'orderDir' => ['bail', 'string', new BackedEnumRule(OrderDirection::ASCENDING)], + 'page' => ['bail', 'integer'], + 'perPage' => ['bail', 'integer'], + ]; + } + + public function getOrderBy(): ?UserOrderBy + { + return UserOrderBy::tryFrom($this->input('orderBy')); + } + + public function getOrderDirection(): ?OrderDirection + { + return OrderDirection::tryFrom($this->input('orderDir')); + } + + public function getPage(): ?int + { + return is_null($this->input('page')) + ? null + : $this->input('page'); + } + + public function getPerPage(): ?int + { + return is_null($this->input('perPage')) + ? null + : $this->input('perPage'); + } +} diff --git a/app/Http/Requests/User/GetUserRequest.php b/app/Http/Requests/User/GetUserRequest.php new file mode 100644 index 0000000..24543d8 --- /dev/null +++ b/app/Http/Requests/User/GetUserRequest.php @@ -0,0 +1,33 @@ + + */ + public function rules(): array + { + return []; + } + + public function getUserModel(): User + { + return $this->route('userID'); + } +} diff --git a/app/Http/Requests/User/UpdateUserRequest.php b/app/Http/Requests/User/UpdateUserRequest.php new file mode 100644 index 0000000..48a3fc9 --- /dev/null +++ b/app/Http/Requests/User/UpdateUserRequest.php @@ -0,0 +1,52 @@ + + */ + public function rules(): array + { + return [ + 'email' => ['required', 'email', 'max:250'], + 'name' => ['required', 'string', 'max:250'], + 'password' => ['required', 'string'], + ]; + } + + public function getName(): string + { + return $this->input('name'); + } + + public function getEmail(): string + { + return $this->input('email'); + } + + public function getUserPassword(): string + { + return $this->input('password'); + } + + public function getUserModel(): User + { + return $this->route('userID'); + } +} diff --git a/app/Http/Resources/User/UserResource.php b/app/Http/Resources/User/UserResource.php new file mode 100644 index 0000000..f8e3f25 --- /dev/null +++ b/app/Http/Resources/User/UserResource.php @@ -0,0 +1,30 @@ + + */ + public function toArray(Request $request): array + { + /** @var User */ + $resource = $this->resource; + + return [ + 'id' => $resource->id, + 'name' => $resource->name, + 'email' => $resource->email, + 'created_at' => optional($resource->created_at)->format(DatetimeFormat::ISO_WITH_MILLIS->value), + 'updated_at' => optional($resource->updated_at)->format(DatetimeFormat::ISO_WITH_MILLIS->value), + ]; + } +} diff --git a/app/Models/Permission/Permission.php b/app/Models/Permission/Permission.php new file mode 100644 index 0000000..c668d5f --- /dev/null +++ b/app/Models/Permission/Permission.php @@ -0,0 +1,17 @@ + - */ - protected $fillable = [ - 'name', - 'email', - 'password', - ]; - - /** - * The attributes that should be hidden for serialization. - * - * @var array - */ - protected $hidden = [ - 'password', - 'remember_token', - ]; - - /** - * The attributes that should be cast. - * - * @var array - */ - protected $casts = [ - 'email_verified_at' => 'datetime', - ]; -} diff --git a/app/Models/User/Enum/UserExceptionCode.php b/app/Models/User/Enum/UserExceptionCode.php new file mode 100644 index 0000000..8eb5107 --- /dev/null +++ b/app/Models/User/Enum/UserExceptionCode.php @@ -0,0 +1,9 @@ + + */ + protected $hidden = [ + 'password', + 'remember_token', + ]; + + /** + * The attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'email_verified_at' => 'datetime', + ]; + + public static function findByIdOrFail(int $id): self + { + $user = self::query()->where('id', $id)->first(); + + if (is_null($user)) { + + throw new ModelNotFoundException(new ExceptionMessageStandard( + 'User ID is not found', + ExceptionErrorCode::MODEL_NOT_FOUND->value, + )); + } + + return $user; + } + + protected static function newFactory() + { + return UserFactory::new(); + } +} diff --git a/app/Port/Core/User/CreateUserPort.php b/app/Port/Core/User/CreateUserPort.php new file mode 100644 index 0000000..99cdc74 --- /dev/null +++ b/app/Port/Core/User/CreateUserPort.php @@ -0,0 +1,10 @@ +bind(Randomizer::class, function (Application $app) { + return new RandomizerUUID; + }); + } +} diff --git a/app/Providers/CoreBinder/CoreBinderLogger.php b/app/Providers/CoreBinder/CoreBinderLogger.php new file mode 100644 index 0000000..e89a99c --- /dev/null +++ b/app/Providers/CoreBinder/CoreBinderLogger.php @@ -0,0 +1,17 @@ +bind(LoggerMessageFormatterFactoryContract::class, function (Application $app) { + return new LoggerMessageFormatterFactory; + }); + } +} diff --git a/app/Providers/CoreBinder/CoreBinderUser.php b/app/Providers/CoreBinder/CoreBinderUser.php new file mode 100644 index 0000000..3252eac --- /dev/null +++ b/app/Providers/CoreBinder/CoreBinderUser.php @@ -0,0 +1,17 @@ +bind(UserCoreContract::class, function (Application $app) { + return new UserCore; + }); + } +} diff --git a/app/Providers/CoreServiceProvider.php b/app/Providers/CoreServiceProvider.php new file mode 100644 index 0000000..b1bcf87 --- /dev/null +++ b/app/Providers/CoreServiceProvider.php @@ -0,0 +1,27 @@ +coreBinders as $classNameBinder) { + /** @var \App\Providers\CoreBinder\CoreBinder */ + $coreBinder = new $classNameBinder; + + $coreBinder->bootCore($this->app); + } + } +} diff --git a/app/Providers/ModelBinding/ModelBinding.php b/app/Providers/ModelBinding/ModelBinding.php new file mode 100644 index 0000000..b30b83b --- /dev/null +++ b/app/Providers/ModelBinding/ModelBinding.php @@ -0,0 +1,17 @@ +exceptionMessage); + } catch (Exception $e) { + throw new InternalServerErrorException(new ExceptionMessageGeneric); + } + }); + } + + public function registerPattern(): void + { + Route::pattern('userID', '[0-9]+'); + } +} diff --git a/app/Providers/RouteServiceProvider.php b/app/Providers/RouteServiceProvider.php index 1d9865b..94cf4cf 100644 --- a/app/Providers/RouteServiceProvider.php +++ b/app/Providers/RouteServiceProvider.php @@ -19,6 +19,11 @@ class RouteServiceProvider extends ServiceProvider */ public const HOME = '/'; + protected $modelBindings = [ + \App\Providers\ModelBinding\ModelBindingUser::class, + ]; + + /** * Define your route model bindings, pattern filters, and other route configuration. * @@ -26,18 +31,26 @@ class RouteServiceProvider extends ServiceProvider */ public function boot() { + $this->configureModelBindings(); $this->configureRateLimiting(); $this->routes(function () { Route::middleware('api') - ->prefix('api') ->group(base_path('routes/api.php')); - - Route::middleware('web') - ->group(base_path('routes/web.php')); }); } + protected function configureModelBindings(): void + { + foreach ($this->modelBindings as $binding) { + /** @var \App\Providers\ModelBinding\ModelBinding */ + $bindingClass = new $binding; + + $bindingClass->bindModel(); + $bindingClass->registerPattern(); + } + } + /** * Configure the rate limiters for the application. * diff --git a/app/Rules/Date/DateISORule.php b/app/Rules/Date/DateISORule.php new file mode 100644 index 0000000..8682ea5 --- /dev/null +++ b/app/Rules/Date/DateISORule.php @@ -0,0 +1,28 @@ +value) + ) { + return; + } + + $fail('The :attribute should be ISO formatted datetime in UTC timezone with millisecond'); + } +} diff --git a/app/Rules/Enum/BackedEnumRule.php b/app/Rules/Enum/BackedEnumRule.php new file mode 100644 index 0000000..436f14e --- /dev/null +++ b/app/Rules/Enum/BackedEnumRule.php @@ -0,0 +1,53 @@ +enum->value); + + if ($enumValueType !== get_debug_type($value)) { + $value = $this->castValue($enumValueType, $value); + } + + $this->enum->from($value); + } catch (ValueError $e) { + $fail("The :attribute should only contain: {$this->getStringifiedValidValue()}"); + } + } + + protected function castValue(string $type, mixed $value): mixed + { + if ($type === 'int') { + return intval($value); + } + + if ($type === 'string') { + return (string) $value; + } + } + + protected function getStringifiedValidValue(): string + { + return collect($this->enum->cases())->implode(function (BackedEnum $enum) { + return $enum->value; + }, ', '); + } +} diff --git a/composer.json b/composer.json index d7220d5..c848360 100644 --- a/composer.json +++ b/composer.json @@ -10,18 +10,20 @@ "require": { "php": "^8.0.2", "guzzlehttp/guzzle": "^7.2", - "laravel/framework": "^9.19", + "laravel/framework": "^10.0", "laravel/sanctum": "^3.0", - "laravel/tinker": "^2.7" + "laravel/tinker": "^2.7", + "spatie/laravel-permission": "^5.9" }, "require-dev": { + "brianium/paratest": "^7.0", "fakerphp/faker": "^1.9.1", "laravel/pint": "^1.0", "laravel/sail": "^1.0.1", "mockery/mockery": "^1.4.4", - "nunomaduro/collision": "^6.1", - "phpunit/phpunit": "^9.5.10", - "spatie/laravel-ignition": "^1.0" + "nunomaduro/collision": "^7.0", + "phpunit/phpunit": "^10.0", + "spatie/laravel-ignition": "^2.0" }, "autoload": { "psr-4": { @@ -67,6 +69,6 @@ "pestphp/pest-plugin": true } }, - "minimum-stability": "dev", + "minimum-stability": "stable", "prefer-stable": true } diff --git a/composer.lock b/composer.lock index cf17b1d..06552bf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,30 +4,29 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ccbd816a07b206f971042295b899d1ba", + "content-hash": "ef19be2db5a210740dffdc2de9cb8591", "packages": [ { "name": "brick/math", - "version": "0.10.2", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f" + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/459f2781e1a08d52ee56b0b1444086e038561e3f", - "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f", + "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478", + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478", "shasum": "" }, "require": { - "ext-json": "*", - "php": "^7.4 || ^8.0" + "php": "^8.0" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^9.0", - "vimeo/psalm": "4.25.0" + "vimeo/psalm": "5.0.0" }, "type": "library", "autoload": { @@ -52,7 +51,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.10.2" + "source": "https://github.com/brick/math/tree/0.11.0" }, "funding": [ { @@ -60,7 +59,7 @@ "type": "github" } ], - "time": "2022-08-10T22:54:19+00:00" + "time": "2023-01-15T23:15:59+00:00" }, { "name": "dflydev/dot-access-data", @@ -137,49 +136,6 @@ }, "time": "2022-10-27T11:44:00+00:00" }, - { - "name": "doctrine/deprecations", - "version": "v1.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/deprecations.git", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "shasum": "" - }, - "require": { - "php": "^7.1|^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5|^8.5|^9.5", - "psr/log": "^1|^2|^3" - }, - "suggest": { - "psr/log": "Allows logging deprecations via PSR-3 logger implementation" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", - "homepage": "https://www.doctrine-project.org/", - "support": { - "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" - }, - "time": "2022-05-02T15:47:09+00:00" - }, { "name": "doctrine/inflector", "version": "2.0.6", @@ -273,28 +229,27 @@ }, { "name": "doctrine/lexer", - "version": "2.1.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" + "reference": "84a527db05647743d50373e0ec53a152f2cde568" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568", + "reference": "84a527db05647743d50373e0ec53a152f2cde568", "shasum": "" }, "require": { - "doctrine/deprecations": "^1.0", - "php": "^7.1 || ^8.0" + "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", - "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "doctrine/coding-standard": "^10", + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9.5", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.0" + "vimeo/psalm": "^5.0" }, "type": "library", "autoload": { @@ -331,7 +286,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.0" + "source": "https://github.com/doctrine/lexer/tree/3.0.0" }, "funding": [ { @@ -347,7 +302,7 @@ "type": "tidelift" } ], - "time": "2022-12-14T08:49:07+00:00" + "time": "2022-12-15T16:57:16+00:00" }, { "name": "dragonmantank/cron-expression", @@ -412,26 +367,26 @@ }, { "name": "egulias/email-validator", - "version": "3.2.5", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "b531a2311709443320c786feb4519cfaf94af796" + "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/b531a2311709443320c786feb4519cfaf94af796", - "reference": "b531a2311709443320c786feb4519cfaf94af796", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/3a85486b709bc384dae8eb78fb2eec649bdb64ff", + "reference": "3a85486b709bc384dae8eb78fb2eec649bdb64ff", "shasum": "" }, "require": { - "doctrine/lexer": "^1.2|^2", - "php": ">=7.2", - "symfony/polyfill-intl-idn": "^1.15" + "doctrine/lexer": "^2.0 || ^3.0", + "php": ">=8.1", + "symfony/polyfill-intl-idn": "^1.26" }, "require-dev": { - "phpunit/phpunit": "^8.5.8|^9.3.3", - "vimeo/psalm": "^4" + "phpunit/phpunit": "^9.5.27", + "vimeo/psalm": "^4.30" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -439,7 +394,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0.x-dev" + "dev-master": "4.0.x-dev" } }, "autoload": { @@ -467,7 +422,7 @@ ], "support": { "issues": "https://github.com/egulias/EmailValidator/issues", - "source": "https://github.com/egulias/EmailValidator/tree/3.2.5" + "source": "https://github.com/egulias/EmailValidator/tree/4.0.1" }, "funding": [ { @@ -475,7 +430,7 @@ "type": "github" } ], - "time": "2023-01-02T17:26:14+00:00" + "time": "2023-01-14T14:17:03+00:00" }, { "name": "fruitcake/php-cors", @@ -550,24 +505,24 @@ }, { "name": "graham-campbell/result-type", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/GrahamCampbell/Result-Type.git", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8" + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8", - "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", + "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", - "phpoption/phpoption": "^1.9" + "phpoption/phpoption": "^1.9.1" }, "require-dev": { - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" }, "type": "library", "autoload": { @@ -596,7 +551,7 @@ ], "support": { "issues": "https://github.com/GrahamCampbell/Result-Type/issues", - "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0" + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1" }, "funding": [ { @@ -608,26 +563,26 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:56:11+00:00" + "time": "2023-02-25T20:23:15+00:00" }, { "name": "guzzlehttp/guzzle", - "version": "7.5.0", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b964ca597e86b752cd994f27293e9fa6b6a95ed9", + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -720,7 +675,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + "source": "https://github.com/guzzle/guzzle/tree/7.5.1" }, "funding": [ { @@ -736,7 +691,7 @@ "type": "tidelift" } ], - "time": "2022-08-28T15:39:27+00:00" + "time": "2023-04-17T16:30:08+00:00" }, { "name": "guzzlehttp/promises", @@ -824,22 +779,22 @@ }, { "name": "guzzlehttp/psr7", - "version": "2.4.3", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "67c26b443f348a51926030c83481b85718457d3d" + "reference": "b635f279edd83fc275f822a1188157ffea568ff6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", - "reference": "67c26b443f348a51926030c83481b85718457d3d", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0", "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", + "psr/http-message": "^1.1 || ^2.0", "ralouphie/getallheaders": "^3.0" }, "provide": { @@ -859,9 +814,6 @@ "bamarni-bin": { "bin-links": true, "forward-command": false - }, - "branch-alias": { - "dev-master": "2.4-dev" } }, "autoload": { @@ -923,7 +875,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.3" + "source": "https://github.com/guzzle/psr7/tree/2.5.0" }, "funding": [ { @@ -939,51 +891,143 @@ "type": "tidelift" } ], - "time": "2022-10-26T14:07:24+00:00" + "time": "2023-04-17T16:11:26+00:00" + }, + { + "name": "guzzlehttp/uri-template", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/uri-template.git", + "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/uri-template/zipball/b945d74a55a25a949158444f09ec0d3c120d69e2", + "reference": "b945d74a55a25a949158444f09ec0d3c120d69e2", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.19 || ^9.5.8", + "uri-template/tests": "1.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\UriTemplate\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + } + ], + "description": "A polyfill class for uri_template of PHP", + "keywords": [ + "guzzlehttp", + "uri-template" + ], + "support": { + "issues": "https://github.com/guzzle/uri-template/issues", + "source": "https://github.com/guzzle/uri-template/tree/v1.0.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/uri-template", + "type": "tidelift" + } + ], + "time": "2021-10-07T12:57:01+00:00" }, { "name": "laravel/framework", - "version": "v9.46.0", + "version": "v10.8.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "62b05b6de5733d89378a279e40230a71e5ab5d92" + "reference": "317d7ccaeb1bbf4ac3035efe225ef2746c45f3a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/62b05b6de5733d89378a279e40230a71e5ab5d92", - "reference": "62b05b6de5733d89378a279e40230a71e5ab5d92", + "url": "https://api.github.com/repos/laravel/framework/zipball/317d7ccaeb1bbf4ac3035efe225ef2746c45f3a8", + "reference": "317d7ccaeb1bbf4ac3035efe225ef2746c45f3a8", "shasum": "" }, "require": { - "doctrine/inflector": "^2.0", + "brick/math": "^0.9.3|^0.10.2|^0.11", + "composer-runtime-api": "^2.2", + "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.3.2", - "egulias/email-validator": "^3.2.1", + "egulias/email-validator": "^3.2.1|^4.0", + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", "ext-mbstring": "*", "ext-openssl": "*", + "ext-session": "*", + "ext-tokenizer": "*", "fruitcake/php-cors": "^1.2", - "laravel/serializable-closure": "^1.2.2", + "guzzlehttp/uri-template": "^1.0", + "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", - "monolog/monolog": "^2.0", + "monolog/monolog": "^3.0", "nesbot/carbon": "^2.62.1", "nunomaduro/termwind": "^1.13", - "php": "^8.0.2", + "php": "^8.1", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^6.0.9", - "symfony/error-handler": "^6.0", - "symfony/finder": "^6.0", - "symfony/http-foundation": "^6.0", - "symfony/http-kernel": "^6.0", - "symfony/mailer": "^6.0", - "symfony/mime": "^6.0", - "symfony/process": "^6.0", - "symfony/routing": "^6.0", - "symfony/uid": "^6.0", - "symfony/var-dumper": "^6.0", + "symfony/console": "^6.2", + "symfony/error-handler": "^6.2", + "symfony/finder": "^6.2", + "symfony/http-foundation": "^6.2", + "symfony/http-kernel": "^6.2", + "symfony/mailer": "^6.2", + "symfony/mime": "^6.2", + "symfony/process": "^6.2", + "symfony/routing": "^6.2", + "symfony/uid": "^6.2", + "symfony/var-dumper": "^6.2", "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.4.1", "voku/portable-ascii": "^2.0" @@ -1019,6 +1063,7 @@ "illuminate/notifications": "self.version", "illuminate/pagination": "self.version", "illuminate/pipeline": "self.version", + "illuminate/process": "self.version", "illuminate/queue": "self.version", "illuminate/redis": "self.version", "illuminate/routing": "self.version", @@ -1032,7 +1077,8 @@ "require-dev": { "ably/ably-php": "^1.0", "aws/aws-sdk-php": "^3.235.5", - "doctrine/dbal": "^2.13.3|^3.1.4", + "doctrine/dbal": "^3.5.1", + "ext-gmp": "*", "fakerphp/faker": "^1.21", "guzzlehttp/guzzle": "^7.5", "league/flysystem-aws-s3-v3": "^3.0", @@ -1041,23 +1087,27 @@ "league/flysystem-read-only": "^3.3", "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", - "orchestra/testbench-core": "^7.16", + "orchestra/testbench-core": "^8.4", "pda/pheanstalk": "^4.0", + "phpstan/phpdoc-parser": "^1.15", "phpstan/phpstan": "^1.4.7", - "phpunit/phpunit": "^9.5.8", - "predis/predis": "^1.1.9|^2.0.2", - "symfony/cache": "^6.0" + "phpunit/phpunit": "^10.0.7", + "predis/predis": "^2.0.2", + "symfony/cache": "^6.2", + "symfony/http-client": "^6.2.4" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", "brianium/paratest": "Required to run tests in parallel (^6.0).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.13.3|^3.1.4).", - "ext-bcmath": "Required to use the multiple_of validation rule.", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^3.5.1).", + "ext-apcu": "Required to use the APC cache driver.", + "ext-fileinfo": "Required to use the Filesystem class.", "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", "ext-memcached": "Required to use the memcache cache driver.", - "ext-pcntl": "Required to use all features of the queue worker.", + "ext-pcntl": "Required to use all features of the queue worker and console signal trapping.", + "ext-pdo": "Required to use all database features.", "ext-posix": "Required to use all features of the queue worker.", "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", @@ -1072,21 +1122,21 @@ "mockery/mockery": "Required to use mocking (^1.5.1).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8).", - "predis/predis": "Required to use the predis connector (^1.1.9|^2.0.2).", + "phpunit/phpunit": "Required to use assertions and run tests (^9.5.8|^10.0.7).", + "predis/predis": "Required to use the predis connector (^2.0.2).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^6.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^6.0).", - "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.0).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.0).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^6.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^6.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^6.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^6.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^6.2).", "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.x-dev" + "dev-master": "10.x-dev" } }, "autoload": { @@ -1125,33 +1175,33 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-01-03T15:12:31+00:00" + "time": "2023-04-18T13:45:33+00:00" }, { "name": "laravel/sanctum", - "version": "v3.1.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "d823587299d50c3ae9c26fa1731ef0d5b725c788" + "reference": "d09d69bac55708fcd4a3b305d760e673d888baf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/d823587299d50c3ae9c26fa1731ef0d5b725c788", - "reference": "d823587299d50c3ae9c26fa1731ef0d5b725c788", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/d09d69bac55708fcd4a3b305d760e673d888baf9", + "reference": "d09d69bac55708fcd4a3b305d760e673d888baf9", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^9.21", - "illuminate/contracts": "^9.21", - "illuminate/database": "^9.21", - "illuminate/support": "^9.21", + "illuminate/console": "^9.21|^10.0", + "illuminate/contracts": "^9.21|^10.0", + "illuminate/database": "^9.21|^10.0", + "illuminate/support": "^9.21|^10.0", "php": "^8.0.2" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^7.0", + "orchestra/testbench": "^7.0|^8.0", "phpunit/phpunit": "^9.3" }, "type": "library", @@ -1190,20 +1240,20 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2023-01-03T09:38:46+00:00" + "time": "2023-01-13T15:41:49+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.2.2", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae" + "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/47afb7fae28ed29057fdca37e16a84f90cc62fae", - "reference": "47afb7fae28ed29057fdca37e16a84f90cc62fae", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", + "reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37", "shasum": "" }, "require": { @@ -1250,26 +1300,26 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2022-09-08T13:45:54+00:00" + "time": "2023-01-30T18:31:20+00:00" }, { "name": "laravel/tinker", - "version": "v2.7.3", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef" + "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/5062061b4924af3392225dd482ca7b4d85d8b8ef", - "reference": "5062061b4924af3392225dd482ca7b4d85d8b8ef", + "url": "https://api.github.com/repos/laravel/tinker/zipball/04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", + "reference": "04a2d3bd0d650c0764f70bf49d1ee39393e4eb10", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0|^9.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0", + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", "php": "^7.2.5|^8.0", "psy/psysh": "^0.10.4|^0.11.1", "symfony/var-dumper": "^4.3.4|^5.0|^6.0" @@ -1279,7 +1329,7 @@ "phpunit/phpunit": "^8.5.8|^9.3.3" }, "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0)." + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0)." }, "type": "library", "extra": { @@ -1316,22 +1366,22 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.7.3" + "source": "https://github.com/laravel/tinker/tree/v2.8.1" }, - "time": "2022-11-09T15:11:38+00:00" + "time": "2023-02-15T16:40:09+00:00" }, { "name": "league/commonmark", - "version": "2.3.8", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47" + "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/c493585c130544c4e91d2e0e131e6d35cb0cbc47", - "reference": "c493585c130544c4e91d2e0e131e6d35cb0cbc47", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048", "shasum": "" }, "require": { @@ -1367,7 +1417,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.4-dev" + "dev-main": "2.5-dev" } }, "autoload": { @@ -1424,7 +1474,7 @@ "type": "tidelift" } ], - "time": "2022-12-10T16:02:17+00:00" + "time": "2023-03-24T15:16:10+00:00" }, { "name": "league/config", @@ -1510,16 +1560,16 @@ }, { "name": "league/flysystem", - "version": "3.12.0", + "version": "3.14.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "2aef65a47e44f2d6f9938f720f6dd697e7ba7b76" + "reference": "e2a279d7f47d9098e479e8b21f7fb8b8de230158" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2aef65a47e44f2d6f9938f720f6dd697e7ba7b76", - "reference": "2aef65a47e44f2d6f9938f720f6dd697e7ba7b76", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/e2a279d7f47d9098e479e8b21f7fb8b8de230158", + "reference": "e2a279d7f47d9098e479e8b21f7fb8b8de230158", "shasum": "" }, "require": { @@ -1536,7 +1586,7 @@ "require-dev": { "async-aws/s3": "^1.5", "async-aws/simple-s3": "^1.1", - "aws/aws-sdk-php": "^3.198.1", + "aws/aws-sdk-php": "^3.220.0", "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", @@ -1581,7 +1631,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.12.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.14.0" }, "funding": [ { @@ -1591,13 +1641,9 @@ { "url": "https://github.com/frankdejonge", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/league/flysystem", - "type": "tidelift" } ], - "time": "2022-12-20T20:21:10+00:00" + "time": "2023-04-11T18:11:47+00:00" }, { "name": "league/mime-type-detection", @@ -1657,42 +1703,41 @@ }, { "name": "monolog/monolog", - "version": "2.8.0", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "720488632c590286b88b80e62aa3d3d551ad4a50" + "reference": "9b5daeaffce5b926cac47923798bba91059e60e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/720488632c590286b88b80e62aa3d3d551ad4a50", - "reference": "720488632c590286b88b80e62aa3d3d551ad4a50", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/9b5daeaffce5b926cac47923798bba91059e60e2", + "reference": "9b5daeaffce5b926cac47923798bba91059e60e2", "shasum": "" }, "require": { - "php": ">=7.2", - "psr/log": "^1.0.1 || ^2.0 || ^3.0" + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" }, "provide": { - "psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0" + "psr/log-implementation": "3.0.0" }, "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "aws/aws-sdk-php": "^3.0", "doctrine/couchdb": "~1.0@dev", "elasticsearch/elasticsearch": "^7 || ^8", "ext-json": "*", - "graylog2/gelf-php": "^1.4.2", - "guzzlehttp/guzzle": "^7.4", + "graylog2/gelf-php": "^1.4.2 || ^2@dev", + "guzzlehttp/guzzle": "^7.4.5", "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpspec/prophecy": "^1.15", - "phpstan/phpstan": "^0.12.91", - "phpunit/phpunit": "^8.5.14", - "predis/predis": "^1.1 || ^2.0", - "rollbar/rollbar": "^1.3 || ^2 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^9.5.26", + "predis/predis": "^1.1 || ^2", "ruflin/elastica": "^7", - "swiftmailer/swiftmailer": "^5.3|^6.0", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -1715,7 +1760,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { @@ -1743,7 +1788,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/2.8.0" + "source": "https://github.com/Seldaek/monolog/tree/3.3.1" }, "funding": [ { @@ -1755,20 +1800,20 @@ "type": "tidelift" } ], - "time": "2022-07-24T11:55:47+00:00" + "time": "2023-02-06T13:46:10+00:00" }, { "name": "nesbot/carbon", - "version": "2.64.1", + "version": "2.66.0", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "f2e59963f4c4f4fdfb9fcfd752e8d2e2b79a4e2c" + "reference": "496712849902241f04902033b0441b269effe001" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/f2e59963f4c4f4fdfb9fcfd752e8d2e2b79a4e2c", - "reference": "f2e59963f4c4f4fdfb9fcfd752e8d2e2b79a4e2c", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/496712849902241f04902033b0441b269effe001", + "reference": "496712849902241f04902033b0441b269effe001", "shasum": "" }, "require": { @@ -1857,7 +1902,7 @@ "type": "tidelift" } ], - "time": "2023-01-01T23:17:36+00:00" + "time": "2023-01-29T18:53:47+00:00" }, { "name": "nette/schema", @@ -1923,28 +1968,30 @@ }, { "name": "nette/utils", - "version": "v3.2.8", + "version": "v4.0.0", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368" + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/02a54c4c872b99e4ec05c4aec54b5a06eb0f6368", - "reference": "02a54c4c872b99e4ec05c4aec54b5a06eb0f6368", + "url": "https://api.github.com/repos/nette/utils/zipball/cacdbf5a91a657ede665c541eda28941d4b09c1e", + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e", "shasum": "" }, "require": { - "php": ">=7.2 <8.3" + "php": ">=8.0 <8.3" }, "conflict": { - "nette/di": "<3.0.6" + "nette/finder": "<3", + "nette/schema": "<1.2.2" }, "require-dev": { - "nette/tester": "~2.0", + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.4", "phpstan/phpstan": "^1.0", - "tracy/tracy": "^2.3" + "tracy/tracy": "^2.9" }, "suggest": { "ext-gd": "to use Image", @@ -1958,7 +2005,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-master": "4.0-dev" } }, "autoload": { @@ -2002,22 +2049,22 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v3.2.8" + "source": "https://github.com/nette/utils/tree/v4.0.0" }, - "time": "2022-09-12T23:36:20+00:00" + "time": "2023-02-02T10:41:53+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.2", + "version": "v4.15.4", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc" + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", - "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", "shasum": "" }, "require": { @@ -2058,22 +2105,22 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" }, - "time": "2022-11-12T15:38:23+00:00" + "time": "2023-03-05T19:49:14+00:00" }, { "name": "nunomaduro/termwind", - "version": "v1.15.0", + "version": "v1.15.1", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d" + "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/594ab862396c16ead000de0c3c38f4a5cbe1938d", - "reference": "594ab862396c16ead000de0c3c38f4a5cbe1938d", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/8ab0b32c8caa4a2e09700ea32925441385e4a5dc", + "reference": "8ab0b32c8caa4a2e09700ea32925441385e4a5dc", "shasum": "" }, "require": { @@ -2130,7 +2177,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v1.15.0" + "source": "https://github.com/nunomaduro/termwind/tree/v1.15.1" }, "funding": [ { @@ -2146,28 +2193,28 @@ "type": "github" } ], - "time": "2022-12-20T19:00:15+00:00" + "time": "2023-02-08T01:06:31+00:00" }, { "name": "phpoption/phpoption", - "version": "1.9.0", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab" + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", - "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e", + "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8", - "phpunit/phpunit": "^8.5.28 || ^9.5.21" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12" }, "type": "library", "extra": { @@ -2209,7 +2256,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.0" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.1" }, "funding": [ { @@ -2221,7 +2268,7 @@ "type": "tidelift" } ], - "time": "2022-07-30T15:51:26+00:00" + "time": "2023-02-25T19:38:58+00:00" }, { "name": "psr/container", @@ -2328,21 +2375,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -2362,7 +2409,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -2374,27 +2421,27 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client/tree/1.0.2" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-04-10T20:12:12+00:00" }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -2414,7 +2461,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -2429,31 +2476,31 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2023-04-10T20:10:41+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "2.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "2.0.x-dev" } }, "autoload": { @@ -2468,7 +2515,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP messages", @@ -2482,9 +2529,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/2.0" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:54:51+00:00" }, { "name": "psr/log", @@ -2589,16 +2636,16 @@ }, { "name": "psy/psysh", - "version": "v0.11.10", + "version": "v0.11.15", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "e9eadffbed9c9deb5426fd107faae0452bf20a36" + "reference": "5350ce0ec8ecf2c5b5cf554cd2496f97b444af85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/e9eadffbed9c9deb5426fd107faae0452bf20a36", - "reference": "e9eadffbed9c9deb5426fd107faae0452bf20a36", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/5350ce0ec8ecf2c5b5cf554cd2496f97b444af85", + "reference": "5350ce0ec8ecf2c5b5cf554cd2496f97b444af85", "shasum": "" }, "require": { @@ -2659,9 +2706,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.10" + "source": "https://github.com/bobthecow/psysh/tree/v0.11.15" }, - "time": "2022-12-23T17:47:18+00:00" + "time": "2023-04-07T21:57:09+00:00" }, { "name": "ralouphie/getallheaders", @@ -2798,20 +2845,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.1", + "version": "4.7.4", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "a1acf96007170234a8399586a6e2ab8feba109d1" + "reference": "60a4c63ab724854332900504274f6150ff26d286" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/a1acf96007170234a8399586a6e2ab8feba109d1", - "reference": "a1acf96007170234a8399586a6e2ab8feba109d1", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286", + "reference": "60a4c63ab724854332900504274f6150ff26d286", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -2874,7 +2921,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.1" + "source": "https://github.com/ramsey/uuid/tree/4.7.4" }, "funding": [ { @@ -2886,20 +2933,102 @@ "type": "tidelift" } ], - "time": "2022-12-31T22:20:34+00:00" + "time": "2023-04-15T23:01:58+00:00" + }, + { + "name": "spatie/laravel-permission", + "version": "5.10.1", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "d08b3ffc5870cce4a47a39f22174947b33c191ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/d08b3ffc5870cce4a47a39f22174947b33c191ae", + "reference": "d08b3ffc5870cce4a47a39f22174947b33c191ae", + "shasum": "" + }, + "require": { + "illuminate/auth": "^7.0|^8.0|^9.0|^10.0", + "illuminate/container": "^7.0|^8.0|^9.0|^10.0", + "illuminate/contracts": "^7.0|^8.0|^9.0|^10.0", + "illuminate/database": "^7.0|^8.0|^9.0|^10.0", + "php": "^7.3|^8.0" + }, + "require-dev": { + "orchestra/testbench": "^5.0|^6.0|^7.0|^8.0", + "phpunit/phpunit": "^9.4", + "predis/predis": "^1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.x-dev", + "dev-master": "5.x-dev" + }, + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 6.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/5.10.1" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2023-04-12T17:08:32+00:00" }, { "name": "symfony/console", - "version": "v6.2.3", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0f579613e771dba2dbb8211c382342a641f5da06" + "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0f579613e771dba2dbb8211c382342a641f5da06", - "reference": "0f579613e771dba2dbb8211c382342a641f5da06", + "url": "https://api.github.com/repos/symfony/console/zipball/3582d68a64a86ec25240aaa521ec8bc2342b369b", + "reference": "3582d68a64a86ec25240aaa521ec8bc2342b369b", "shasum": "" }, "require": { @@ -2961,12 +3090,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.2.3" + "source": "https://github.com/symfony/console/tree/v6.2.8" }, "funding": [ { @@ -2982,20 +3111,20 @@ "type": "tidelift" } ], - "time": "2022-12-28T14:26:22+00:00" + "time": "2023-03-29T21:42:15+00:00" }, { "name": "symfony/css-selector", - "version": "v6.2.3", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "ab1df4ba3ded7b724766ba3a6e0eca0418e74f80" + "reference": "aedf3cb0f5b929ec255d96bbb4909e9932c769e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/ab1df4ba3ded7b724766ba3a6e0eca0418e74f80", - "reference": "ab1df4ba3ded7b724766ba3a6e0eca0418e74f80", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/aedf3cb0f5b929ec255d96bbb4909e9932c769e0", + "reference": "aedf3cb0f5b929ec255d96bbb4909e9932c769e0", "shasum": "" }, "require": { @@ -3031,7 +3160,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.2.3" + "source": "https://github.com/symfony/css-selector/tree/v6.2.7" }, "funding": [ { @@ -3047,20 +3176,20 @@ "type": "tidelift" } ], - "time": "2022-12-28T14:26:22+00:00" + "time": "2023-02-14T08:44:56+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3" + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3", - "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", "shasum": "" }, "require": { @@ -3098,7 +3227,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" }, "funding": [ { @@ -3114,20 +3243,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-03-01T10:25:55+00:00" }, { "name": "symfony/error-handler", - "version": "v6.2.3", + "version": "v6.2.9", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "0926124c95d220499e2baf0fb465772af3a4eddb" + "reference": "e95f1273b3953c3b5e5341172dae838bacee11ee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/0926124c95d220499e2baf0fb465772af3a4eddb", - "reference": "0926124c95d220499e2baf0fb465772af3a4eddb", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/e95f1273b3953c3b5e5341172dae838bacee11ee", + "reference": "e95f1273b3953c3b5e5341172dae838bacee11ee", "shasum": "" }, "require": { @@ -3169,7 +3298,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.2.3" + "source": "https://github.com/symfony/error-handler/tree/v6.2.9" }, "funding": [ { @@ -3185,20 +3314,20 @@ "type": "tidelift" } ], - "time": "2022-12-19T14:33:49+00:00" + "time": "2023-04-11T16:03:19+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.2.2", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "3ffeb31139b49bf6ef0bc09d1db95eac053388d1" + "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3ffeb31139b49bf6ef0bc09d1db95eac053388d1", - "reference": "3ffeb31139b49bf6ef0bc09d1db95eac053388d1", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/04046f35fd7d72f9646e721fc2ecb8f9c67d3339", + "reference": "04046f35fd7d72f9646e721fc2ecb8f9c67d3339", "shasum": "" }, "require": { @@ -3252,7 +3381,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.2" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.2.8" }, "funding": [ { @@ -3268,20 +3397,20 @@ "type": "tidelift" } ], - "time": "2022-12-14T16:11:27+00:00" + "time": "2023-03-20T16:06:02+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "0782b0b52a737a05b4383d0df35a474303cabdae" + "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0782b0b52a737a05b4383d0df35a474303cabdae", - "reference": "0782b0b52a737a05b4383d0df35a474303cabdae", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", + "reference": "0ad3b6f1e4e2da5690fefe075cd53a238646d8dd", "shasum": "" }, "require": { @@ -3331,7 +3460,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.2.1" }, "funding": [ { @@ -3347,20 +3476,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-03-01T10:32:47+00:00" }, { "name": "symfony/finder", - "version": "v6.2.3", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "81eefbddfde282ee33b437ba5e13d7753211ae8e" + "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/81eefbddfde282ee33b437ba5e13d7753211ae8e", - "reference": "81eefbddfde282ee33b437ba5e13d7753211ae8e", + "url": "https://api.github.com/repos/symfony/finder/zipball/20808dc6631aecafbe67c186af5dcb370be3a0eb", + "reference": "20808dc6631aecafbe67c186af5dcb370be3a0eb", "shasum": "" }, "require": { @@ -3395,7 +3524,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v6.2.3" + "source": "https://github.com/symfony/finder/tree/v6.2.7" }, "funding": [ { @@ -3411,20 +3540,20 @@ "type": "tidelift" } ], - "time": "2022-12-22T17:55:15+00:00" + "time": "2023-02-16T09:57:23+00:00" }, { "name": "symfony/http-foundation", - "version": "v6.2.2", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "ddf4dd35de1623e7c02013523e6c2137b67b636f" + "reference": "511a524affeefc191939348823ac75e9921c2112" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ddf4dd35de1623e7c02013523e6c2137b67b636f", - "reference": "ddf4dd35de1623e7c02013523e6c2137b67b636f", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/511a524affeefc191939348823ac75e9921c2112", + "reference": "511a524affeefc191939348823ac75e9921c2112", "shasum": "" }, "require": { @@ -3473,7 +3602,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.2.2" + "source": "https://github.com/symfony/http-foundation/tree/v6.2.8" }, "funding": [ { @@ -3489,20 +3618,20 @@ "type": "tidelift" } ], - "time": "2022-12-14T16:11:27+00:00" + "time": "2023-03-29T21:42:15+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.2.4", + "version": "v6.2.9", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "74f2e638ec3fa0315443bd85fab7fc8066b77f83" + "reference": "02246510cf7031726f7237138d61b796b95799b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/74f2e638ec3fa0315443bd85fab7fc8066b77f83", - "reference": "74f2e638ec3fa0315443bd85fab7fc8066b77f83", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/02246510cf7031726f7237138d61b796b95799b3", + "reference": "02246510cf7031726f7237138d61b796b95799b3", "shasum": "" }, "require": { @@ -3511,7 +3640,7 @@ "symfony/deprecation-contracts": "^2.1|^3", "symfony/error-handler": "^6.1", "symfony/event-dispatcher": "^5.4|^6.0", - "symfony/http-foundation": "^5.4|^6.0", + "symfony/http-foundation": "^5.4.21|^6.2.7", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -3584,7 +3713,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.2.4" + "source": "https://github.com/symfony/http-kernel/tree/v6.2.9" }, "funding": [ { @@ -3600,24 +3729,24 @@ "type": "tidelift" } ], - "time": "2022-12-29T19:05:08+00:00" + "time": "2023-04-13T16:41:43+00:00" }, { "name": "symfony/mailer", - "version": "v6.2.2", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "b355ad81f1d2987c47dcd3b04d5dce669e1e62e6" + "reference": "bfcfa015c67e19c6fdb7ca6fe70700af1e740a17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/b355ad81f1d2987c47dcd3b04d5dce669e1e62e6", - "reference": "b355ad81f1d2987c47dcd3b04d5dce669e1e62e6", + "url": "https://api.github.com/repos/symfony/mailer/zipball/bfcfa015c67e19c6fdb7ca6fe70700af1e740a17", + "reference": "bfcfa015c67e19c6fdb7ca6fe70700af1e740a17", "shasum": "" }, "require": { - "egulias/email-validator": "^2.1.10|^3", + "egulias/email-validator": "^2.1.10|^3|^4", "php": ">=8.1", "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", @@ -3633,7 +3762,7 @@ }, "require-dev": { "symfony/console": "^5.4|^6.0", - "symfony/http-client-contracts": "^1.1|^2|^3", + "symfony/http-client": "^5.4|^6.0", "symfony/messenger": "^6.2", "symfony/twig-bridge": "^6.2" }, @@ -3663,7 +3792,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.2.2" + "source": "https://github.com/symfony/mailer/tree/v6.2.8" }, "funding": [ { @@ -3679,20 +3808,20 @@ "type": "tidelift" } ], - "time": "2022-12-14T16:11:27+00:00" + "time": "2023-03-14T15:00:05+00:00" }, { "name": "symfony/mime", - "version": "v6.2.2", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "8c98bf40406e791043890a163f6f6599b9cfa1ed" + "reference": "62e341f80699badb0ad70b31149c8df89a2d778e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/8c98bf40406e791043890a163f6f6599b9cfa1ed", - "reference": "8c98bf40406e791043890a163f6f6599b9cfa1ed", + "url": "https://api.github.com/repos/symfony/mime/zipball/62e341f80699badb0ad70b31149c8df89a2d778e", + "reference": "62e341f80699badb0ad70b31149c8df89a2d778e", "shasum": "" }, "require": { @@ -3708,7 +3837,7 @@ "symfony/serializer": "<6.2" }, "require-dev": { - "egulias/email-validator": "^2.1.10|^3.1", + "egulias/email-validator": "^2.1.10|^3.1|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "symfony/dependency-injection": "^5.4|^6.0", @@ -3746,7 +3875,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.2.2" + "source": "https://github.com/symfony/mime/tree/v6.2.7" }, "funding": [ { @@ -3762,7 +3891,7 @@ "type": "tidelift" } ], - "time": "2022-12-14T16:38:10+00:00" + "time": "2023-02-24T10:42:00+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4424,16 +4553,16 @@ }, { "name": "symfony/process", - "version": "v6.2.0", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "ba6e55359f8f755fe996c58a81e00eaa67a35877" + "reference": "75ed64103df4f6615e15a7fe38b8111099f47416" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/ba6e55359f8f755fe996c58a81e00eaa67a35877", - "reference": "ba6e55359f8f755fe996c58a81e00eaa67a35877", + "url": "https://api.github.com/repos/symfony/process/zipball/75ed64103df4f6615e15a7fe38b8111099f47416", + "reference": "75ed64103df4f6615e15a7fe38b8111099f47416", "shasum": "" }, "require": { @@ -4465,7 +4594,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.2.0" + "source": "https://github.com/symfony/process/tree/v6.2.8" }, "funding": [ { @@ -4481,20 +4610,20 @@ "type": "tidelift" } ], - "time": "2022-11-02T09:08:04+00:00" + "time": "2023-03-09T16:20:02+00:00" }, { "name": "symfony/routing", - "version": "v6.2.3", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "35fec764f3e2c8c08fb340d275c84bc78ca7e0c9" + "reference": "69062e2823f03b82265d73a966999660f0e1e404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/35fec764f3e2c8c08fb340d275c84bc78ca7e0c9", - "reference": "35fec764f3e2c8c08fb340d275c84bc78ca7e0c9", + "url": "https://api.github.com/repos/symfony/routing/zipball/69062e2823f03b82265d73a966999660f0e1e404", + "reference": "69062e2823f03b82265d73a966999660f0e1e404", "shasum": "" }, "require": { @@ -4553,7 +4682,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.2.3" + "source": "https://github.com/symfony/routing/tree/v6.2.8" }, "funding": [ { @@ -4569,20 +4698,20 @@ "type": "tidelift" } ], - "time": "2022-12-20T16:41:15+00:00" + "time": "2023-03-14T15:00:05+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75" + "reference": "a8c9cedf55f314f3a186041d19537303766df09a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/aac98028c69df04ee77eb69b96b86ee51fbf4b75", - "reference": "aac98028c69df04ee77eb69b96b86ee51fbf4b75", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/a8c9cedf55f314f3a186041d19537303766df09a", + "reference": "a8c9cedf55f314f3a186041d19537303766df09a", "shasum": "" }, "require": { @@ -4638,7 +4767,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.2.1" }, "funding": [ { @@ -4654,20 +4783,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-03-01T10:32:47+00:00" }, { "name": "symfony/string", - "version": "v6.2.2", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "863219fd713fa41cbcd285a79723f94672faff4d" + "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/863219fd713fa41cbcd285a79723f94672faff4d", - "reference": "863219fd713fa41cbcd285a79723f94672faff4d", + "url": "https://api.github.com/repos/symfony/string/zipball/193e83bbd6617d6b2151c37fff10fa7168ebddef", + "reference": "193e83bbd6617d6b2151c37fff10fa7168ebddef", "shasum": "" }, "require": { @@ -4724,7 +4853,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.2.2" + "source": "https://github.com/symfony/string/tree/v6.2.8" }, "funding": [ { @@ -4740,20 +4869,20 @@ "type": "tidelift" } ], - "time": "2022-12-14T16:11:27+00:00" + "time": "2023-03-20T16:06:02+00:00" }, { "name": "symfony/translation", - "version": "v6.2.3", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "a2a15404ef4c15d92c205718eb828b225a144379" + "reference": "817535dbb1721df8b3a8f2489dc7e50bcd6209b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/a2a15404ef4c15d92c205718eb828b225a144379", - "reference": "a2a15404ef4c15d92c205718eb828b225a144379", + "url": "https://api.github.com/repos/symfony/translation/zipball/817535dbb1721df8b3a8f2489dc7e50bcd6209b5", + "reference": "817535dbb1721df8b3a8f2489dc7e50bcd6209b5", "shasum": "" }, "require": { @@ -4822,7 +4951,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.2.3" + "source": "https://github.com/symfony/translation/tree/v6.2.8" }, "funding": [ { @@ -4838,20 +4967,20 @@ "type": "tidelift" } ], - "time": "2022-12-23T14:11:11+00:00" + "time": "2023-03-31T09:14:44+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.2.0", + "version": "v3.2.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "68cce71402305a015f8c1589bfada1280dc64fe7" + "reference": "dfec258b9dd17a6b24420d464c43bffe347441c8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/68cce71402305a015f8c1589bfada1280dc64fe7", - "reference": "68cce71402305a015f8c1589bfada1280dc64fe7", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/dfec258b9dd17a6b24420d464c43bffe347441c8", + "reference": "dfec258b9dd17a6b24420d464c43bffe347441c8", "shasum": "" }, "require": { @@ -4903,7 +5032,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.2.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.2.1" }, "funding": [ { @@ -4919,20 +5048,20 @@ "type": "tidelift" } ], - "time": "2022-11-25T10:21:52+00:00" + "time": "2023-03-01T10:32:47+00:00" }, { "name": "symfony/uid", - "version": "v6.2.0", + "version": "v6.2.7", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "4f9f537e57261519808a7ce1d941490736522bbc" + "reference": "d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/4f9f537e57261519808a7ce1d941490736522bbc", - "reference": "4f9f537e57261519808a7ce1d941490736522bbc", + "url": "https://api.github.com/repos/symfony/uid/zipball/d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0", + "reference": "d30c72a63897cfa043e1de4d4dd2ffa9ecefcdc0", "shasum": "" }, "require": { @@ -4977,7 +5106,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.2.0" + "source": "https://github.com/symfony/uid/tree/v6.2.7" }, "funding": [ { @@ -4993,20 +5122,20 @@ "type": "tidelift" } ], - "time": "2022-10-09T08:55:40+00:00" + "time": "2023-02-14T08:44:56+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.2.3", + "version": "v6.2.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "fdbadd4803bc3c96ef89238c9c9e2ebe424ec2e0" + "reference": "d37ab6787be2db993747b6218fcc96e8e3bb4bd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/fdbadd4803bc3c96ef89238c9c9e2ebe424ec2e0", - "reference": "fdbadd4803bc3c96ef89238c9c9e2ebe424ec2e0", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/d37ab6787be2db993747b6218fcc96e8e3bb4bd0", + "reference": "d37ab6787be2db993747b6218fcc96e8e3bb4bd0", "shasum": "" }, "require": { @@ -5065,7 +5194,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.2.3" + "source": "https://github.com/symfony/var-dumper/tree/v6.2.8" }, "funding": [ { @@ -5081,7 +5210,7 @@ "type": "tidelift" } ], - "time": "2022-12-22T17:55:15+00:00" + "time": "2023-03-29T21:42:15+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -5355,36 +5484,58 @@ ], "packages-dev": [ { - "name": "doctrine/instantiator", - "version": "1.5.0", + "name": "brianium/paratest", + "version": "v7.1.3", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" + "url": "https://github.com/paratestphp/paratest.git", + "reference": "f394bb33b2bb7a4120b531e8991409b7aa62fc43" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", - "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/f394bb33b2bb7a4120b531e8991409b7aa62fc43", + "reference": "f394bb33b2bb7a4120b531e8991409b7aa62fc43", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1", + "jean85/pretty-package-versions": "^2.0.5", + "php": "~8.1.0 || ~8.2.0", + "phpunit/php-code-coverage": "^10.1.0", + "phpunit/php-file-iterator": "^4.0.1", + "phpunit/php-timer": "^6.0", + "phpunit/phpunit": "^10.1.0", + "sebastian/environment": "^6.0.1", + "symfony/console": "^6.2.8", + "symfony/process": "^6.2.8" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^11", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.30 || ^5.4" + "doctrine/coding-standard": "^11.1.0", + "ext-pcov": "*", + "ext-posix": "*", + "infection/infection": "^0.26.19", + "phpstan/phpstan": "^1.10.13", + "phpstan/phpstan-deprecation-rules": "^1.1.3", + "phpstan/phpstan-phpunit": "^1.3.11", + "phpstan/phpstan-strict-rules": "^1.5.1", + "squizlabs/php_codesniffer": "^3.7.2", + "symfony/filesystem": "^6.2.7" }, + "bin": [ + "bin/paratest", + "bin/paratest.bat", + "bin/paratest_for_phpstorm" + ], "type": "library", "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + "ParaTest\\": [ + "src/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -5393,36 +5544,39 @@ ], "authors": [ { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "role": "Developer" + }, + { + "name": "Filippo Tessarotto", + "email": "zoeslam@gmail.com", + "role": "Developer" } ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", "keywords": [ - "constructor", - "instantiate" + "concurrent", + "parallel", + "phpunit", + "testing" ], "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v7.1.3" }, "funding": [ { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" + "url": "https://github.com/sponsors/Slamdunk", + "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" + "url": "https://paypal.me/filippotessarotto", + "type": "paypal" } ], - "time": "2022-12-30T00:15:36+00:00" + "time": "2023-04-14T06:17:37+00:00" }, { "name": "fakerphp/faker", @@ -5492,18 +5646,79 @@ }, "time": "2022-12-13T13:54:32+00:00" }, + { + "name": "fidry/cpu-core-counter", + "version": "0.5.1", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "reference": "b58e5a3933e541dc286cc91fc4f3898bbc6f1623", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^9.5.26 || ^8.5.31", + "theofidry/php-cs-fixer-config": "^1.0", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/0.5.1" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2022-12-24T12:35:10+00:00" + }, { "name": "filp/whoops", - "version": "2.14.6", + "version": "2.15.2", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "f7948baaa0330277c729714910336383286305da" + "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/f7948baaa0330277c729714910336383286305da", - "reference": "f7948baaa0330277c729714910336383286305da", + "url": "https://api.github.com/repos/filp/whoops/zipball/aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", + "reference": "aac9304c5ed61bf7b1b7a6064bf9806ab842ce73", "shasum": "" }, "require": { @@ -5553,7 +5768,7 @@ ], "support": { "issues": "https://github.com/filp/whoops/issues", - "source": "https://github.com/filp/whoops/tree/2.14.6" + "source": "https://github.com/filp/whoops/tree/2.15.2" }, "funding": [ { @@ -5561,7 +5776,7 @@ "type": "github" } ], - "time": "2022-11-02T16:23:29+00:00" + "time": "2023-04-12T12:00:00+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -5614,18 +5829,77 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "jean85/pretty-package-versions", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.17", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^0.12.66", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" + }, + "time": "2021-10-08T21:21:46+00:00" + }, { "name": "laravel/pint", - "version": "v1.3.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "6a2c0927b4f6ad4eadb5a67fe3835fdad108949d" + "reference": "eac5ec3d6b5c96543c682e309a10fdddc9f61d80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/6a2c0927b4f6ad4eadb5a67fe3835fdad108949d", - "reference": "6a2c0927b4f6ad4eadb5a67fe3835fdad108949d", + "url": "https://api.github.com/repos/laravel/pint/zipball/eac5ec3d6b5c96543c682e309a10fdddc9f61d80", + "reference": "eac5ec3d6b5c96543c682e309a10fdddc9f61d80", "shasum": "" }, "require": { @@ -5633,16 +5907,16 @@ "ext-mbstring": "*", "ext-tokenizer": "*", "ext-xml": "*", - "php": "^8.0" + "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "~3.13.1", - "illuminate/view": "^9.32.0", - "laravel-zero/framework": "^9.2.0", + "friendsofphp/php-cs-fixer": "^3.16.0", + "illuminate/view": "^10.5.1", + "laravel-zero/framework": "^10.0.2", "mockery/mockery": "^1.5.1", - "nunomaduro/larastan": "^2.2.0", - "nunomaduro/termwind": "^1.14.0", - "pestphp/pest": "^1.22.1" + "nunomaduro/larastan": "^2.5.1", + "nunomaduro/termwind": "^1.15.1", + "pestphp/pest": "^2.4.0" }, "bin": [ "builds/pint" @@ -5678,27 +5952,32 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2022-12-20T17:16:15+00:00" + "time": "2023-04-18T14:50:44+00:00" }, { "name": "laravel/sail", - "version": "v1.17.0", + "version": "v1.21.4", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "7d69da7b2bdb8cbe8da6663eb2ae0e00c884bf80" + "reference": "5e59b4a57181020477e2b18943b27493638e3f89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/7d69da7b2bdb8cbe8da6663eb2ae0e00c884bf80", - "reference": "7d69da7b2bdb8cbe8da6663eb2ae0e00c884bf80", + "url": "https://api.github.com/repos/laravel/sail/zipball/5e59b4a57181020477e2b18943b27493638e3f89", + "reference": "5e59b4a57181020477e2b18943b27493638e3f89", "shasum": "" }, "require": { - "illuminate/console": "^8.0|^9.0", - "illuminate/contracts": "^8.0|^9.0", - "illuminate/support": "^8.0|^9.0", - "php": "^7.3|^8.0" + "illuminate/console": "^8.0|^9.0|^10.0", + "illuminate/contracts": "^8.0|^9.0|^10.0", + "illuminate/support": "^8.0|^9.0|^10.0", + "php": "^7.3|^8.0", + "symfony/yaml": "^6.0" + }, + "require-dev": { + "orchestra/testbench": "^6.0|^7.0|^8.0", + "phpstan/phpstan": "^1.10" }, "bin": [ "bin/sail" @@ -5738,7 +6017,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2022-12-22T14:46:08+00:00" + "time": "2023-03-30T12:28:55+00:00" }, { "name": "mockery/mockery", @@ -5814,16 +6093,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -5861,7 +6140,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -5869,42 +6148,47 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nunomaduro/collision", - "version": "v6.4.0", + "version": "v7.5.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "f05978827b9343cba381ca05b8c7deee346b6015" + "reference": "bbbc6fb9c1ee88f8aa38e47abd15c465f946f85e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f05978827b9343cba381ca05b8c7deee346b6015", - "reference": "f05978827b9343cba381ca05b8c7deee346b6015", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/bbbc6fb9c1ee88f8aa38e47abd15c465f946f85e", + "reference": "bbbc6fb9c1ee88f8aa38e47abd15c465f946f85e", "shasum": "" }, "require": { - "filp/whoops": "^2.14.5", - "php": "^8.0.0", - "symfony/console": "^6.0.2" + "filp/whoops": "^2.15.2", + "nunomaduro/termwind": "^1.15.1", + "php": "^8.1.0", + "symfony/console": "^6.2.8" + }, + "conflict": { + "phpunit/phpunit": "<10.1.0" }, "require-dev": { - "brianium/paratest": "^6.4.1", - "laravel/framework": "^9.26.1", - "laravel/pint": "^1.1.1", - "nunomaduro/larastan": "^1.0.3", - "nunomaduro/mock-final-classes": "^1.1.0", - "orchestra/testbench": "^7.7", - "phpunit/phpunit": "^9.5.23", - "spatie/ignition": "^1.4.1" + "brianium/paratest": "^7.1.3", + "laravel/framework": "^10.7.1", + "laravel/pint": "^1.8.0", + "laravel/sail": "^1.21.4", + "laravel/sanctum": "^3.2.1", + "laravel/tinker": "^2.8.1", + "nunomaduro/larastan": "^2.5.1", + "orchestra/testbench-core": "^8.4.2", + "pestphp/pest": "^2.5.0", + "phpunit/phpunit": "^10.1.0", + "sebastian/environment": "^6.0.1", + "spatie/laravel-ignition": "^2.1.0" }, "type": "library", "extra": { - "branch-alias": { - "dev-develop": "6.x-dev" - }, "laravel": { "providers": [ "NunoMaduro\\Collision\\Adapters\\Laravel\\CollisionServiceProvider" @@ -5912,6 +6196,9 @@ } }, "autoload": { + "files": [ + "./src/Adapters/Phpunit/Autoload.php" + ], "psr-4": { "NunoMaduro\\Collision\\": "src/" } @@ -5957,7 +6244,7 @@ "type": "patreon" } ], - "time": "2023-01-03T12:54:54+00:00" + "time": "2023-04-14T10:39:16+00:00" }, { "name": "phar-io/manifest", @@ -6072,44 +6359,44 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.23", + "version": "10.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c" + "reference": "884a0da7f9f46f28b2cb69134217fd810b793974" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", - "reference": "9f1f0f9a2fbb680b26d1cf9b61b6eac43a6e4e9c", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/884a0da7f9f46f28b2cb69134217fd810b793974", + "reference": "884a0da7f9f46f28b2cb69134217fd810b793974", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.14", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", + "nikic/php-parser": "^4.15", + "php": ">=8.1", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-text-template": "^3.0", + "sebastian/code-unit-reverse-lookup": "^3.0", + "sebastian/complexity": "^3.0", + "sebastian/environment": "^6.0", + "sebastian/lines-of-code": "^2.0", + "sebastian/version": "^4.0", "theseer/tokenizer": "^1.2.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.1" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "10.1-dev" } }, "autoload": { @@ -6137,7 +6424,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.23" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.1" }, "funding": [ { @@ -6145,32 +6433,32 @@ "type": "github" } ], - "time": "2022-12-28T12:41:10+00:00" + "time": "2023-04-17T12:15:40+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "fd9329ab3368f59fe1fe808a189c51086bd4b6bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/fd9329ab3368f59fe1fe808a189c51086bd4b6bd", + "reference": "fd9329ab3368f59fe1fe808a189c51086bd4b6bd", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -6197,7 +6485,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.0.1" }, "funding": [ { @@ -6205,28 +6493,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2023-02-10T16:53:14+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-pcntl": "*" @@ -6234,7 +6522,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -6260,7 +6548,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" }, "funding": [ { @@ -6268,32 +6556,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2023-02-03T06:56:09+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/9f3d3709577a527025f55bcf0f7ab8052c8bb37d", + "reference": "9f3d3709577a527025f55bcf0f7ab8052c8bb37d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -6319,7 +6607,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.0" }, "funding": [ { @@ -6327,32 +6615,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2023-02-03T06:56:46+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6378,7 +6666,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" }, "funding": [ { @@ -6386,24 +6674,23 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2023-02-03T06:57:52+00:00" }, { "name": "phpunit/phpunit", - "version": "9.5.27", + "version": "10.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38" + "reference": "0d9401b7e8245d71079e249e3cb868e9d2337887" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a2bc7ffdca99f92d959b3f2270529334030bba38", - "reference": "a2bc7ffdca99f92d959b3f2270529334030bba38", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0d9401b7e8245d71079e249e3cb868e9d2337887", + "reference": "0d9401b7e8245d71079e249e3cb868e9d2337887", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -6413,27 +6700,26 @@ "myclabs/deep-copy": "^1.10.1", "phar-io/manifest": "^2.0.3", "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.8", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.5", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.2", - "sebastian/version": "^3.0.2" + "php": ">=8.1", + "phpunit/php-code-coverage": "^10.1.1", + "phpunit/php-file-iterator": "^4.0", + "phpunit/php-invoker": "^4.0", + "phpunit/php-text-template": "^3.0", + "phpunit/php-timer": "^6.0", + "sebastian/cli-parser": "^2.0", + "sebastian/code-unit": "^2.0", + "sebastian/comparator": "^5.0", + "sebastian/diff": "^5.0", + "sebastian/environment": "^6.0", + "sebastian/exporter": "^5.0", + "sebastian/global-state": "^6.0", + "sebastian/object-enumerator": "^5.0", + "sebastian/recursion-context": "^5.0", + "sebastian/type": "^4.0", + "sebastian/version": "^4.0" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -6441,7 +6727,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-main": "10.1-dev" } }, "autoload": { @@ -6472,7 +6758,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.27" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.1.1" }, "funding": [ { @@ -6488,32 +6775,32 @@ "type": "tidelift" } ], - "time": "2022-12-09T07:31:23+00:00" + "time": "2023-04-17T12:17:05+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae", + "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -6536,7 +6823,7 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0" }, "funding": [ { @@ -6544,32 +6831,32 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2023-02-03T06:58:15+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", + "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -6592,7 +6879,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" }, "funding": [ { @@ -6600,32 +6887,32 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2023-02-03T06:58:43+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -6647,7 +6934,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" }, "funding": [ { @@ -6655,34 +6942,36 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2023-02-03T06:59:15+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.8", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a" + "reference": "72f01e6586e0caf6af81297897bd112eb7e9627c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", - "reference": "fa0f136dd2334583309d32b62544682ee972b51a", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/72f01e6586e0caf6af81297897bd112eb7e9627c", + "reference": "72f01e6586e0caf6af81297897bd112eb7e9627c", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/diff": "^5.0", + "sebastian/exporter": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -6721,7 +7010,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" + "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.0" }, "funding": [ { @@ -6729,33 +7018,33 @@ "type": "github" } ], - "time": "2022-09-14T12:41:17+00:00" + "time": "2023-02-03T07:07:16+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/e67d240970c9dc7ea7b2123a6d520e334dd61dc6", + "reference": "e67d240970c9dc7ea7b2123a6d520e334dd61dc6", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^4.10", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -6778,7 +7067,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/3.0.0" }, "funding": [ { @@ -6786,33 +7075,33 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-02-03T06:59:47+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/aae9a0a43bff37bd5d8d0311426c87bf36153f02", + "reference": "aae9a0a43bff37bd5d8d0311426c87bf36153f02", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^10.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -6844,7 +7133,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/5.0.1" }, "funding": [ { @@ -6852,27 +7142,27 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2023-03-23T05:12:41+00:00" }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/43c751b41d74f96cbbd4e07b7aec9675651e2951", + "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "suggest": { "ext-posix": "*" @@ -6880,7 +7170,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -6899,7 +7189,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -6907,7 +7197,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/6.0.1" }, "funding": [ { @@ -6915,34 +7206,34 @@ "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2023-04-11T05:39:26+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.5", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" + "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", - "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0", + "reference": "f3ec4bf931c0b31e5b413f5b4fc970a7d03338c0", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.1", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -6984,7 +7275,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.0.0" }, "funding": [ { @@ -6992,38 +7283,35 @@ "type": "github" } ], - "time": "2022-09-14T06:03:37+00:00" + "time": "2023-02-03T07:06:49+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "aab257c712de87b90194febd52e4d184551c2d44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/aab257c712de87b90194febd52e4d184551c2d44", + "reference": "aab257c712de87b90194febd52e4d184551c2d44", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -7048,7 +7336,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.0" }, "funding": [ { @@ -7056,33 +7344,33 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2023-02-03T07:07:38+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/17c4d940ecafb3d15d2cf916f4108f664e28b130", + "reference": "17c4d940ecafb3d15d2cf916f4108f664e28b130", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^4.10", + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "2.0-dev" } }, "autoload": { @@ -7105,7 +7393,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.0" }, "funding": [ { @@ -7113,34 +7401,34 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-02-03T07:08:02+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", + "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.1", + "sebastian/object-reflector": "^3.0", + "sebastian/recursion-context": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7162,7 +7450,7 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" }, "funding": [ { @@ -7170,32 +7458,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2023-02-03T07:08:32+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", + "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -7217,7 +7505,7 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" }, "funding": [ { @@ -7225,32 +7513,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2023-02-03T07:06:18+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "05909fb5bc7df4c52992396d0116aed689f93712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", + "reference": "05909fb5bc7df4c52992396d0116aed689f93712", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -7277,65 +7565,10 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2020-10-26T13:17:30+00:00" - }, - { - "name": "sebastian/resource-operations", - "version": "3.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" }, "funding": [ { @@ -7343,32 +7576,32 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2023-02-03T07:05:40+00:00" }, { "name": "sebastian/type", - "version": "3.2.0", + "version": "4.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e" + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", - "reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", + "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "require-dev": { - "phpunit/phpunit": "^9.5" + "phpunit/phpunit": "^10.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -7391,7 +7624,7 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" }, "funding": [ { @@ -7399,29 +7632,29 @@ "type": "github" } ], - "time": "2022-09-12T14:47:03+00:00" + "time": "2023-02-03T07:10:45+00:00" }, { "name": "sebastian/version", - "version": "3.0.2", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -7444,7 +7677,7 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" }, "funding": [ { @@ -7452,20 +7685,20 @@ "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2023-02-07T11:34:05+00:00" }, { "name": "spatie/backtrace", - "version": "1.2.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "4ee7d41aa5268107906ea8a4d9ceccde136dbd5b" + "reference": "ec4dd16476b802dbdc6b4467f84032837e316b8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/4ee7d41aa5268107906ea8a4d9ceccde136dbd5b", - "reference": "4ee7d41aa5268107906ea8a4d9ceccde136dbd5b", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/ec4dd16476b802dbdc6b4467f84032837e316b8c", + "reference": "ec4dd16476b802dbdc6b4467f84032837e316b8c", "shasum": "" }, "require": { @@ -7474,6 +7707,7 @@ "require-dev": { "ext-json": "*", "phpunit/phpunit": "^9.3", + "spatie/phpunit-snapshot-assertions": "^4.2", "symfony/var-dumper": "^5.1" }, "type": "library", @@ -7501,8 +7735,7 @@ "spatie" ], "support": { - "issues": "https://github.com/spatie/backtrace/issues", - "source": "https://github.com/spatie/backtrace/tree/1.2.1" + "source": "https://github.com/spatie/backtrace/tree/1.4.0" }, "funding": [ { @@ -7514,24 +7747,24 @@ "type": "other" } ], - "time": "2021-11-09T10:57:15+00:00" + "time": "2023-03-04T08:57:24+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.3.2", + "version": "1.3.6", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "609903bd154ba3d71f5e23a91c3b431fa8f71868" + "reference": "530ac81255af79f114344286e4275f8869c671e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/609903bd154ba3d71f5e23a91c3b431fa8f71868", - "reference": "609903bd154ba3d71f5e23a91c3b431fa8f71868", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/530ac81255af79f114344286e4275f8869c671e2", + "reference": "530ac81255af79f114344286e4275f8869c671e2", "shasum": "" }, "require": { - "illuminate/pipeline": "^8.0|^9.0", + "illuminate/pipeline": "^8.0|^9.0|^10.0", "php": "^8.0", "spatie/backtrace": "^1.2", "symfony/http-foundation": "^5.0|^6.0", @@ -7575,7 +7808,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.3.2" + "source": "https://github.com/spatie/flare-client-php/tree/1.3.6" }, "funding": [ { @@ -7583,43 +7816,51 @@ "type": "github" } ], - "time": "2022-12-26T14:36:46+00:00" + "time": "2023-04-12T07:57:12+00:00" }, { "name": "spatie/ignition", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "dd3d456779108d7078baf4e43f8c2b937d9794a1" + "reference": "4db9c9626e4d7745efbe0b512157326190b41b65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/dd3d456779108d7078baf4e43f8c2b937d9794a1", - "reference": "dd3d456779108d7078baf4e43f8c2b937d9794a1", + "url": "https://api.github.com/repos/spatie/ignition/zipball/4db9c9626e4d7745efbe0b512157326190b41b65", + "reference": "4db9c9626e4d7745efbe0b512157326190b41b65", "shasum": "" }, "require": { "ext-json": "*", "ext-mbstring": "*", - "monolog/monolog": "^2.0", "php": "^8.0", + "spatie/backtrace": "^1.4", "spatie/flare-client-php": "^1.1", "symfony/console": "^5.4|^6.0", "symfony/var-dumper": "^5.4|^6.0" }, "require-dev": { + "illuminate/cache": "^9.52", "mockery/mockery": "^1.4", "pestphp/pest": "^1.20", "phpstan/extension-installer": "^1.1", "phpstan/phpstan-deprecation-rules": "^1.0", "phpstan/phpstan-phpunit": "^1.0", - "symfony/process": "^5.4|^6.0" + "psr/simple-cache-implementation": "*", + "symfony/cache": "^6.2", + "symfony/process": "^5.4|^6.0", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "simple-cache-implementation": "To cache solutions from OpenAI" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.2.x-dev" + "dev-main": "1.4.x-dev" } }, "autoload": { @@ -7658,45 +7899,47 @@ "type": "github" } ], - "time": "2022-08-26T11:51:15+00:00" + "time": "2023-04-12T09:07:50+00:00" }, { "name": "spatie/laravel-ignition", - "version": "1.6.4", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "1a2b4bd3d48c72526c0ba417687e5c56b5cf49bc" + "reference": "3718dfb91bc5aff340af26507a61f0f9605f81e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/1a2b4bd3d48c72526c0ba417687e5c56b5cf49bc", - "reference": "1a2b4bd3d48c72526c0ba417687e5c56b5cf49bc", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/3718dfb91bc5aff340af26507a61f0f9605f81e8", + "reference": "3718dfb91bc5aff340af26507a61f0f9605f81e8", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "illuminate/support": "^8.77|^9.27", - "monolog/monolog": "^2.3", - "php": "^8.0", - "spatie/flare-client-php": "^1.0.1", - "spatie/ignition": "^1.4.1", - "symfony/console": "^5.0|^6.0", - "symfony/var-dumper": "^5.0|^6.0" + "illuminate/support": "^10.0", + "php": "^8.1", + "spatie/flare-client-php": "^1.3.5", + "spatie/ignition": "^1.5.0", + "symfony/console": "^6.2.3", + "symfony/var-dumper": "^6.2.3" }, "require-dev": { - "filp/whoops": "^2.14", - "livewire/livewire": "^2.8|dev-develop", - "mockery/mockery": "^1.4", - "nunomaduro/larastan": "^1.0", - "orchestra/testbench": "^6.23|^7.0", - "pestphp/pest": "^1.20", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-phpunit": "^1.0", - "spatie/laravel-ray": "^1.27" + "livewire/livewire": "^2.11", + "mockery/mockery": "^1.5.1", + "openai-php/client": "^0.3.4", + "orchestra/testbench": "^8.0", + "pestphp/pest": "^1.22.3", + "phpstan/extension-installer": "^1.2", + "phpstan/phpstan-deprecation-rules": "^1.1.1", + "phpstan/phpstan-phpunit": "^1.3.3", + "vlucas/phpdotenv": "^5.5" + }, + "suggest": { + "openai-php/client": "Require get solutions from OpenAI", + "psr/simple-cache-implementation": "Needed to cache solutions from OpenAI" }, "type": "library", "extra": { @@ -7748,7 +7991,81 @@ "type": "github" } ], - "time": "2023-01-03T19:28:04+00:00" + "time": "2023-04-12T09:26:00+00:00" + }, + { + "name": "symfony/yaml", + "version": "v6.2.7", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "e8e6a1d59e050525f27a1f530aa9703423cb7f57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e8e6a1d59e050525f27a1f530aa9703423cb7f57", + "reference": "e8e6a1d59e050525f27a1f530aa9703423cb7f57", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<5.4" + }, + "require-dev": { + "symfony/console": "^5.4|^6.0" + }, + "suggest": { + "symfony/console": "For validating YAML files using the lint command" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v6.2.7" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-16T09:57:23+00:00" }, { "name": "theseer/tokenizer", @@ -7802,7 +8119,7 @@ } ], "aliases": [], - "minimum-stability": "dev", + "minimum-stability": "stable", "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, diff --git a/config/app.php b/config/app.php index ef76a7e..a28bc57 100644 --- a/config/app.php +++ b/config/app.php @@ -185,6 +185,7 @@ /* * Package Service Providers... */ + Spatie\Permission\PermissionServiceProvider::class, /* * Application Service Providers... @@ -192,6 +193,7 @@ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, + App\Providers\CoreServiceProvider::class, App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, diff --git a/config/hashing.php b/config/hashing.php index bcd3be4..3a75e20 100644 --- a/config/hashing.php +++ b/config/hashing.php @@ -15,7 +15,7 @@ | */ - 'driver' => 'bcrypt', + 'driver' => 'argon2id', /* |-------------------------------------------------------------------------- diff --git a/config/logging.php b/config/logging.php index 5aa1dbb..d69873d 100644 --- a/config/logging.php +++ b/config/logging.php @@ -53,7 +53,7 @@ 'channels' => [ 'stack' => [ 'driver' => 'stack', - 'channels' => ['single'], + 'channels' => ['daily', 'stderr'], 'ignore_exceptions' => false, ], @@ -85,7 +85,7 @@ 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), - 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), + 'connectionString' => 'tls://' . env('PAPERTRAIL_URL') . ':' . env('PAPERTRAIL_PORT'), ], ], diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 0000000..020863c --- /dev/null +++ b/config/permission.php @@ -0,0 +1,161 @@ + [ + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * Eloquent model should be used to retrieve your permissions. Of course, it + * is often just the "Permission" model but you may use whatever you like. + * + * The model you want to use as a Permission model needs to implement the + * `Spatie\Permission\Contracts\Permission` contract. + */ + + 'permission' => App\Models\Permission\Permission::class, + + /* + * When using the "HasRoles" trait from this package, we need to know which + * Eloquent model should be used to retrieve your roles. Of course, it + * is often just the "Role" model but you may use whatever you like. + * + * The model you want to use as a Role model needs to implement the + * `Spatie\Permission\Contracts\Role` contract. + */ + + 'role' => App\Models\Permission\Role::class, + + ], + + 'table_names' => [ + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'roles' => 'roles', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your permissions. We have chosen a basic + * default value but you may easily change it to any table you like. + */ + + 'permissions' => 'permissions', + + /* + * When using the "HasPermissions" trait from this package, we need to know which + * table should be used to retrieve your models permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_permissions' => 'model_has_permissions', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your models roles. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'model_has_roles' => 'model_has_roles', + + /* + * When using the "HasRoles" trait from this package, we need to know which + * table should be used to retrieve your roles permissions. We have chosen a + * basic default value but you may easily change it to any table you like. + */ + + 'role_has_permissions' => 'role_has_permissions', + ], + + 'column_names' => [ + /* + * Change this if you want to name the related pivots other than defaults + */ + 'role_pivot_key' => null, //default 'role_id', + 'permission_pivot_key' => null, //default 'permission_id', + + /* + * Change this if you want to name the related model primary key other than + * `model_id`. + * + * For example, this would be nice if your primary keys are all UUIDs. In + * that case, name this `model_uuid`. + */ + + 'model_morph_key' => 'model_id', + + /* + * Change this if you want to use the teams feature and your related model's + * foreign key is other than `team_id`. + */ + + 'team_foreign_key' => 'team_id', + ], + + /* + * When set to true, the method for checking permissions will be registered on the gate. + * Set this to false, if you want to implement custom logic for checking permissions. + */ + + 'register_permission_check_method' => true, + + /* + * When set to true the package implements teams using the 'team_foreign_key'. If you want + * the migrations to register the 'team_foreign_key', you must set this to true + * before doing the migration. If you already did the migration then you must make a new + * migration to also add 'team_foreign_key' to 'roles', 'model_has_roles', and + * 'model_has_permissions'(view the latest version of package's migration file) + */ + + 'teams' => false, + + /* + * When set to true, the required permission names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_permission_in_exception' => false, + + /* + * When set to true, the required role names are added to the exception + * message. This could be considered an information leak in some contexts, so + * the default setting is false here for optimum safety. + */ + + 'display_role_in_exception' => false, + + /* + * By default wildcard permission lookups are disabled. + */ + + 'enable_wildcard_permission' => false, + + 'cache' => [ + + /* + * By default all permissions are cached for 24 hours to speed up performance. + * When permissions or roles are updated the cache is flushed automatically. + */ + + 'expiration_time' => \DateInterval::createFromDateString('24 hours'), + + /* + * The cache key used to store all permissions. + */ + + 'key' => 'spatie.permission.cache', + + /* + * You may optionally indicate a specific cache driver to use for permission and + * role caching using any of the `store` drivers listed in the cache.php config + * file. Using 'default' here means to use the `default` set in cache.php. + */ + + 'store' => 'default', + ], +]; diff --git a/database/factories/UserFactory.php b/database/factories/User/UserFactory.php similarity index 96% rename from database/factories/UserFactory.php rename to database/factories/User/UserFactory.php index 41f8ae8..0377479 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/User/UserFactory.php @@ -1,6 +1,6 @@ bigIncrements('id'); // permission id + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + + $table->unique(['name', 'guard_name']); + }); + + Schema::create($tableNames['roles'], function (Blueprint $table) use ($teams, $columnNames) { + $table->bigIncrements('id'); // role id + if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing + $table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable(); + $table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index'); + } + $table->string('name'); // For MySQL 8.0 use string('name', 125); + $table->string('guard_name'); // For MySQL 8.0 use string('guard_name', 125); + $table->timestamps(); + if ($teams || config('permission.testing')) { + $table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']); + } else { + $table->unique(['name', 'guard_name']); + } + }); + + Schema::create($tableNames['model_has_permissions'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { + $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index'); + + $table->foreign(PermissionRegistrar::$pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } else { + $table->primary([PermissionRegistrar::$pivotPermission, $columnNames['model_morph_key'], 'model_type'], + 'model_has_permissions_permission_model_type_primary'); + } + + }); + + Schema::create($tableNames['model_has_roles'], function (Blueprint $table) use ($tableNames, $columnNames, $teams) { + $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + + $table->string('model_type'); + $table->unsignedBigInteger($columnNames['model_morph_key']); + $table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index'); + + $table->foreign(PermissionRegistrar::$pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + if ($teams) { + $table->unsignedBigInteger($columnNames['team_foreign_key']); + $table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index'); + + $table->primary([$columnNames['team_foreign_key'], PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } else { + $table->primary([PermissionRegistrar::$pivotRole, $columnNames['model_morph_key'], 'model_type'], + 'model_has_roles_role_model_type_primary'); + } + }); + + Schema::create($tableNames['role_has_permissions'], function (Blueprint $table) use ($tableNames) { + $table->unsignedBigInteger(PermissionRegistrar::$pivotPermission); + $table->unsignedBigInteger(PermissionRegistrar::$pivotRole); + + $table->foreign(PermissionRegistrar::$pivotPermission) + ->references('id') // permission id + ->on($tableNames['permissions']) + ->onDelete('cascade'); + + $table->foreign(PermissionRegistrar::$pivotRole) + ->references('id') // role id + ->on($tableNames['roles']) + ->onDelete('cascade'); + + $table->primary([PermissionRegistrar::$pivotPermission, PermissionRegistrar::$pivotRole], 'role_has_permissions_permission_id_role_id_primary'); + }); + + app('cache') + ->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null) + ->forget(config('permission.cache.key')); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $tableNames = config('permission.table_names'); + + if (empty($tableNames)) { + throw new \Exception('Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.'); + } + + Schema::drop($tableNames['role_has_permissions']); + Schema::drop($tableNames['model_has_roles']); + Schema::drop($tableNames['model_has_permissions']); + Schema::drop($tableNames['roles']); + Schema::drop($tableNames['permissions']); + } +} diff --git a/database/migrations/2023_04_20_020152_add_deleted_at_in_users_table.php b/database/migrations/2023_04_20_020152_add_deleted_at_in_users_table.php new file mode 100644 index 0000000..a7bda58 --- /dev/null +++ b/database/migrations/2023_04_20_020152_add_deleted_at_in_users_table.php @@ -0,0 +1,28 @@ +softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropSoftDeletes(); + }); + } +}; diff --git a/phpunit.xml b/phpunit.xml index 2ac86a1..bc1100e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,31 +1,31 @@ - - - - ./tests/Unit - - - ./tests/Feature - - - - - ./app - - - - - - - - - - - - - + + + + ./tests/Unit + + + ./tests/Feature + + + + + + + + + + + + + + + + + ./app + + + ./app + + diff --git a/routes/api.php b/routes/api.php index eb6fa48..f345fdc 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,19 +1,11 @@ get('/user', function (Request $request) { - return $request->user(); -}); +Route::get('users', [UserController::class, 'index'])->name('user.index'); +Route::post('users', [UserController::class, 'store'])->name('user.store'); +Route::delete('users/{userID}', [UserController::class, 'destroy'])->name('user.destroy'); +Route::get('users/{userID}', [UserController::class, 'show'])->name('user.show'); +Route::put('users/{userID}', [UserController::class, 'update'])->name('user.update'); diff --git a/routes/web.php b/routes/web.php deleted file mode 100644 index b130397..0000000 --- a/routes/web.php +++ /dev/null @@ -1,18 +0,0 @@ - + */ + public function rules(): array + { + return [ + // + ]; + } +} diff --git a/tests/Feature/BaseFeatureTestCase.php b/tests/Feature/BaseFeatureTestCase.php new file mode 100644 index 0000000..c02c631 --- /dev/null +++ b/tests/Feature/BaseFeatureTestCase.php @@ -0,0 +1,107 @@ +mockXRequestIdHeader(); + } + + protected function getMockedRequestId(): string + { + return $this->mockedRequestId; + } + + protected function mockXRequestIdHeader(): void + { + $this->mockedRequestId = $this->faker->uuid; + + $this->instance(Randomizer::class, $this->mock( + Randomizer::class, + function (MockInterface $mock) { + $mock->shouldReceive('getRandomizeString') + ->andReturn($this->mockedRequestId); + } + )); + } + + protected function validateLoggingBegin( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + string $endpoint, + string $message, + array $input, + ): bool { + try { + $this->assertSame($endpoint, $argEndpoint); + $this->assertSame($this->getMockedRequestId(), $argRequestID); + $this->assertSame(ProcessingStatus::BEGIN, $argProcessingStatus); + $this->assertSame($message, $argMessage); + $this->assertSame([ + 'input' => $input, + ], $argMeta); + return true; + } catch (Exception $e) { + dd($e); + } + } + + protected function validateLoggingError( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + string $endpoint, + Exception $e, + ): bool { + try { + $this->assertSame($endpoint, $argEndpoint); + $this->assertSame($this->getMockedRequestId(), $argRequestID); + $this->assertSame(ProcessingStatus::ERROR, $argProcessingStatus); + $this->assertSame($e->getMessage(), $argMessage); + $this->assertSame([ + 'trace' => $e->getTrace(), + ], $argMeta); + return true; + } catch (Exception $e) { + dd($e); + } + } + + protected function validateLoggingSuccess( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + string $endpoint, + string $message, + ): bool { + try { + $this->assertSame($endpoint, $argEndpoint); + $this->assertSame($this->getMockedRequestId(), $argRequestID); + $this->assertSame(ProcessingStatus::SUCCESS, $argProcessingStatus); + $this->assertSame($message, $argMessage); + $this->assertSame([], $argMeta); + return true; + } catch (Exception $e) { + dd($e); + } + } +} diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php deleted file mode 100644 index 1eafba6..0000000 --- a/tests/Feature/ExampleTest.php +++ /dev/null @@ -1,21 +0,0 @@ -get('/'); - - $response->assertStatus(200); - } -} diff --git a/tests/Feature/User/CreateUserTest.php b/tests/Feature/User/CreateUserTest.php new file mode 100644 index 0000000..fb772b8 --- /dev/null +++ b/tests/Feature/User/CreateUserTest.php @@ -0,0 +1,521 @@ +resourceAssertion = new ResourceAssertionUser; + + $this->instance(UserCoreContract::class, $this->mock(UserCoreContract::class)); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $this->mock(LoggerMessageFormatterFactoryContract::class) + ); + Log::partialMock(); + } + + #[Test] + #[DataProvider('invalidDataProvider')] + public function should_show_422_when_input_is_invalid( + string $errorMaker, + array $input + ) { + // Act + $response = $this->postJson( + $this->getEndpointUrl(), + $input + ); + + + // Assert + $response->assertUnprocessable(); + $response->assertJsonValidationErrorFor($errorMaker, 'errors.meta'); + } + + public static function invalidDataProvider(): array + { + return [ + 'without email' => [ + 'email', + collect(self::validRequestInput()) + ->except('email') + ->toArray(), + ], + 'email is null' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => null, + ])->toArray(), + ], + 'email is empty string' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => '', + ])->toArray(), + ], + 'email is not in right format (now contain random string)' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => fake()->words(3, true), + ])->toArray(), + ], + 'email is not in right format (now contain array)' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => [fake()->words(3, true)], + ])->toArray(), + ], + 'email should be less than 250 (currently 251)' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => fake()->regexify('[a-z]{241}@gmail.com'), + ])->toArray(), + ], + + 'without name' => [ + 'name', + collect(self::validRequestInput()) + ->except('name') + ->toArray(), + ], + 'name is null' => [ + 'name', + collect(self::validRequestInput()) + ->replace([ + 'name' => null, + ])->toArray(), + ], + 'name is empty string' => [ + 'name', + collect(self::validRequestInput()) + ->replace([ + 'name' => null, + ])->toArray(), + ], + 'name is more than 250 (currently 251)' => [ + 'name', + collect(self::validRequestInput()) + ->replace([ + 'name' => fake()->regexify('[a-z]{251}'), + ])->toArray(), + ], + + 'without password' => [ + 'password', + collect(self::validRequestInput()) + ->except('password') + ->toArray(), + ], + 'password is null' => [ + 'password', + collect(self::validRequestInput()) + ->replace([ + 'password' => null, + ])->toArray(), + ], + 'password is empty string' => [ + 'password', + collect(self::validRequestInput()) + ->replace([ + 'password' => null, + ])->toArray(), + ], + ]; + } + + #[Test] + public function should_show_409_when_thrown_duplicated_email() + { + // Assert + $input = $this->validRequestInput(); + + $mockedExceptionResponse = collect(['foo' => 'bar']); + /** @var ExceptionMessage */ + $mockExceptionMessage = $this->mock( + ExceptionMessage::class, + function (MockInterface $mock) use ($mockedExceptionResponse) { + $mock->shouldReceive('getJsonResponse') + ->andReturn($mockedExceptionResponse); + $mock->shouldReceive('getMessage'); + } + ); + $mockException = new UserEmailDuplicatedException($mockExceptionMessage); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($mockException, $input) { + $mock->shouldReceive('create') + ->once() + ->withArgs(fn ( + CreateUserPort $argInput + ) => $this->validateRequest($argInput, $input)) + ->andThrow($mockException); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logErrorValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logErrorValue, + $mockException, + $input, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + 'Create user endpoint', + $input, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingError( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + $mockException, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logErrorValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('error') + ->with($logErrorValue) + ->once(); + + + // Act + $response = $this->postJson( + $this->getEndpointUrl(), + $input + ); + + + // Assert + $response->assertStatus(Response::HTTP_CONFLICT); + $response->assertJsonPath('errors', $mockedExceptionResponse->toArray()); + } + + #[Test] + public function should_show_500_when_generic_error_is_thrown() + { + // Assert + $input = $this->validRequestInput(); + $exceptionMessage = new ExceptionMessageGeneric; + + $mockException = new Exception('generic error'); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($input, $mockException) { + $mock->shouldReceive('create') + ->once() + ->withArgs(fn ( + CreateUserPort $argInput + ) => $this->validateRequest($argInput, $input)) + ->andThrow($mockException); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logErrorValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logErrorValue, + $mockException, + $input, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + 'Create user endpoint', + $input, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingError( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + $mockException, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logErrorValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('error') + ->with($logErrorValue) + ->once(); + + + // Act + $response = $this->postJson( + $this->getEndpointUrl(), + $input + ); + + + // Assert + $response->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR); + $response->assertJsonPath( + 'errors', + $exceptionMessage->getJsonResponse()->toArray() + ); + } + + #[Test] + public function should_show_201_when_successfully_create_user() + { + // Assert + $input = $this->validRequestInput(); + /** @var User */ + $mockedUser = User::factory()->create(); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($input, $mockedUser) { + $mock->shouldReceive('create') + ->once() + ->withArgs(fn ( + CreateUserPort $argInput + ) => $this->validateRequest($argInput, $input)) + ->andReturn($mockedUser); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logSuccessValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logSuccessValue, + $input, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + 'Create user endpoint', + $input, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingSuccess( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + 'Create user endpoint', + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logSuccessValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('info') + ->with($logSuccessValue) + ->once(); + + + // Act + $response = $this->postJson( + $this->getEndpointUrl(), + $input + ); + + + // Assert + $response->assertStatus(Response::HTTP_CREATED); + $this->resourceAssertion->assertResource($this, $response); + } + + protected function getEndpointInfo(): string + { + return "POST {$this->getEndpointUrl()}"; + } + + protected function getEndpointUrl(): string + { + return route('user.store'); + } + + protected function validateRequest( + CreateUserPort $argInput, + array $input + ): bool { + $this->assertSame($input['email'], $argInput->getEmail()); + $this->assertSame($input['name'], $argInput->getName()); + $this->assertSame($input['password'], $argInput->getUserPassword()); + return true; + } + + protected static function validRequestInput(): array + { + return [ + 'email' => 'faisal@budiono.com', + 'name' => 'faisal budiono', + 'password' => 'password', + ]; + } +} diff --git a/tests/Feature/User/DeleteUserTest.php b/tests/Feature/User/DeleteUserTest.php new file mode 100644 index 0000000..4a73295 --- /dev/null +++ b/tests/Feature/User/DeleteUserTest.php @@ -0,0 +1,281 @@ +user = User::factory()->create([ + 'id' => $this->faker()->numberBetween(1, 100), + ]); + + $this->instance(UserCoreContract::class, $this->mock(UserCoreContract::class)); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $this->mock(LoggerMessageFormatterFactoryContract::class) + ); + Log::partialMock(); + } + + #[Test] + public function should_show_404_when_user_id_is_not_found() + { + // Arrange + $notFoundId = $this->user->id + 1; + + + // Act + $response = $this->deleteJson( + $this->getEndpointUrl($notFoundId), + ); + + + // Assert + $response->assertNotFound(); + } + + #[Test] + public function should_show_500_when_thrown_generic_error() + { + // Arrange + $exceptionMessage = new ExceptionMessageGeneric; + + $mockException = new Exception('generic error'); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($mockException) { + $mock->shouldReceive('delete') + ->once() + ->withArgs(fn ( + DeleteUserPort $argInput + ) => $this->validateRequest($argInput)) + ->andThrow($mockException); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logErrorValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logErrorValue, + $mockException, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Delete user endpoint', + [], + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingError( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + $mockException, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logErrorValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('error') + ->with($logErrorValue) + ->once(); + + + // Act + $response = $this->deleteJson( + $this->getEndpointUrl($this->user->id), + ); + + + // Assert + $response->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR); + $response->assertJsonPath('errors', $exceptionMessage->getJsonResponse()->toArray()); + } + + #[Test] + public function should_show_204_when_successfully_delete_user() + { + // Arrange + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) { + $mock->shouldReceive('delete') + ->once() + ->withArgs(fn ( + DeleteUserPort $argInput + ) => $this->validateRequest($argInput)); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logSuccessValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logSuccessValue, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Delete user endpoint', + [], + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingSuccess( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Delete user endpoint', + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logSuccessValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('info') + ->with($logSuccessValue) + ->once(); + + // Act + $response = $this->deleteJson( + $this->getEndpointUrl($this->user->id), + ); + + + // Assert + $response->assertNoContent(); + } + + protected function getEndpointInfo(int $userId): string + { + return "DELETE {$this->getEndpointUrl($userId)}"; + } + + protected function getEndpointUrl(int $userId): string + { + return route('user.destroy', ['userID' => $userId]); + } + + protected function validateRequest(DeleteUserPort $argInput): bool + { + try { + $this->assertTrue($argInput->getUserModel()->is($this->user)); + return true; + } catch (Exception $e) { + dd($e); + } + } +} diff --git a/tests/Feature/User/GetAllUserTest.php b/tests/Feature/User/GetAllUserTest.php new file mode 100644 index 0000000..d3d52d1 --- /dev/null +++ b/tests/Feature/User/GetAllUserTest.php @@ -0,0 +1,500 @@ +count(10)->create(); + + $this->resourceAssertion = new ResourceAssertionUserList; + + $this->instance(UserCoreContract::class, $this->mock(UserCoreContract::class)); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $this->mock(LoggerMessageFormatterFactoryContract::class) + ); + Log::partialMock(); + } + + #[Test] + #[DataProvider('invalidDataProvider')] + public function should_show_422_when_input_is_invalid( + string $errorMaker, + array $input + ) { + // Act + $response = $this->getJson( + $this->getEndpointUrl() . '?' . http_build_query($input), + ); + + + // Assert + $response->assertUnprocessable(); + $response->assertJsonValidationErrorFor($errorMaker, 'errors.meta'); + } + + public static function invalidDataProvider(): array + { + return [ + 'orderBy is not string (now contain array)' => [ + 'orderBy', + collect(self::validRequestInput()) + ->replace([ + 'orderBy' => ['kuda'], + ])->toArray(), + ], + 'orderBy is not string (now contain integer)' => [ + 'orderBy', + collect(self::validRequestInput()) + ->replace([ + 'orderBy' => 123, + ])->toArray(), + ], + 'orderBy is not valid enum (now contain random string)' => [ + 'orderBy', + collect(self::validRequestInput()) + ->replace([ + 'orderBy' => 'kuda', + ])->toArray(), + ], + + 'orderDir is not string (now contain array)' => [ + 'orderDir', + collect(self::validRequestInput()) + ->replace([ + 'orderDir' => ['kuda'], + ])->toArray(), + ], + 'orderDir is not string (now contain integer)' => [ + 'orderDir', + collect(self::validRequestInput()) + ->replace([ + 'orderDir' => 123, + ])->toArray(), + ], + 'orderDir is not valid enum (now contain random string)' => [ + 'orderDir', + collect(self::validRequestInput()) + ->replace([ + 'orderDir' => 'kuda', + ])->toArray(), + ], + + 'page is not integer (now contain array)' => [ + 'page', + collect(self::validRequestInput()) + ->replace([ + 'page' => [2], + ])->toArray(), + ], + 'page is not integer (now contain string)' => [ + 'page', + collect(self::validRequestInput()) + ->replace([ + 'page' => 'kambing', + ])->toArray(), + ], + + 'perPage is not integer (now contain array)' => [ + 'perPage', + collect(self::validRequestInput()) + ->replace([ + 'perPage' => [2], + ])->toArray(), + ], + 'perPage is not integer (now contain string)' => [ + 'perPage', + collect(self::validRequestInput()) + ->replace([ + 'perPage' => 'kambing', + ])->toArray(), + ], + ]; + } + + #[Test] + public function should_show_500_when_generic_error_is_thrown() + { + // Arrange + $input = $this->validRequestInput(); + $exceptionMessage = new ExceptionMessageGeneric; + + $mockException = new Exception('generic error'); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($input, $mockException) { + $mock->shouldReceive('getAll') + ->once() + ->withArgs(fn ( + GetAllUserPort $argInput + ) => $this->validateRequest($argInput, $input)) + ->andThrow($mockException); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logErrorValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logErrorValue, + $mockException, + $input, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + 'Get all user endpoint', + collect($input) + ->map(fn ($val) => (string) $val) + ->toArray(), + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingError( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + $mockException, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logErrorValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('error') + ->with($logErrorValue) + ->once(); + + + // Act + $response = $this->getJson( + $this->getEndpointUrl() . '?' . http_build_query($input), + $input + ); + + + // Assert + $response->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR); + $response->assertJsonPath( + 'errors', + $exceptionMessage->getJsonResponse()->toArray() + ); + } + + #[Test] + #[DataProvider('validDataProvider')] + public function should_show_200_when_successfully_get_user_list( + array $input, + array $loggedInput, + ) { + // Assert + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($input) { + $mock->shouldReceive('getAll') + ->once() + ->withArgs(fn ( + GetAllUserPort $argInput + ) => $this->validateRequest($argInput, $input)) + ->andReturn(User::query()->paginate()); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logSuccessValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logSuccessValue, + $loggedInput, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + 'Get all user endpoint', + $loggedInput, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingSuccess( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo(), + 'Get all user endpoint', + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logSuccessValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('info') + ->with($logSuccessValue) + ->once(); + + + // Act + $response = $this->getJson( + $this->getEndpointUrl() . '?' . http_build_query($input), + $input + ); + + + // Assert + $response->assertOk(); + $this->resourceAssertion->assertResource($this, $response); + } + + public static function validDataProvider(): array + { + return [ + 'complete data' => [ + collect(self::validRequestInput()) + ->toArray(), + collect(self::validRequestInput()) + ->map(fn ($val) => (string) $val) + ->toArray(), + ], + + 'orderBy is null' => [ + collect(self::validRequestInput()) + ->replace([ + 'orderBy' => null + ])->toArray(), + collect(self::validRequestInput()) + ->except('orderBy') + ->map(fn ($val) => (string) $val) + ->toArray(), + ], + 'without orderBy' => [ + collect(self::validRequestInput()) + ->except('orderBy') + ->toArray(), + collect(self::validRequestInput()) + ->except('orderBy') + ->map(fn ($val) => (string) $val) + ->toArray(), + ], + + 'orderDir is null' => [ + collect(self::validRequestInput()) + ->replace([ + 'orderDir' => null + ])->toArray(), + collect(self::validRequestInput()) + ->except('orderDir') + ->map(fn ($val) => (string) $val) + ->toArray(), + ], + 'without orderDir' => [ + collect(self::validRequestInput()) + ->except('orderDir') + ->toArray(), + collect(self::validRequestInput()) + ->except('orderDir') + ->map(fn ($val) => (string) $val) + ->toArray(), + ], + + 'page is null' => [ + collect(self::validRequestInput()) + ->replace([ + 'page' => null + ])->toArray(), + collect(self::validRequestInput()) + ->except('page') + ->map(fn ($val) => (string) $val) + ->toArray(), + ], + 'without page' => [ + collect(self::validRequestInput()) + ->except('page') + ->toArray(), + collect(self::validRequestInput()) + ->except('page') + ->map(fn ($val) => (string) $val) + ->toArray(), + ], + + 'perPage is null' => [ + collect(self::validRequestInput()) + ->replace([ + 'perPage' => null + ])->toArray(), + collect(self::validRequestInput()) + ->except('perPage') + ->map(fn ($val) => (string) $val) + ->toArray(), + ], + 'without perPage' => [ + collect(self::validRequestInput()) + ->except('perPage') + ->toArray(), + collect(self::validRequestInput()) + ->except('perPage') + ->map(fn ($val) => (string) $val) + ->toArray(), + ], + ]; + } + + protected function getEndpointInfo(): string + { + return "GET {$this->getEndpointUrl()}"; + } + + protected function getEndpointUrl(): string + { + return route('user.index'); + } + + protected function validateRequest( + GetAllUserPort $argInput, + array $input + ): bool { + try { + $this->assertSame( + $input['orderBy'] ?? null, + optional($argInput->getOrderBy())->value + ); + $this->assertSame( + $input['orderDir'] ?? null, + optional($argInput->getOrderDirection())->value + ); + $this->assertSame( + $input['page'] ?? null, + $argInput->getPage() + ); + $this->assertSame( + $input['perPage'] ?? null, + $argInput->getPerPage() + ); + return true; + } catch (Exception $e) { + dd($e); + } + } + + protected static function validRequestInput(): array + { + return [ + 'orderBy' => UserOrderBy::NAME->value, + 'orderDir' => OrderDirection::ASCENDING->value, + 'page' => 1, + 'perPage' => 15, + ]; + } +} diff --git a/tests/Feature/User/GetUserTest.php b/tests/Feature/User/GetUserTest.php new file mode 100644 index 0000000..bd22fef --- /dev/null +++ b/tests/Feature/User/GetUserTest.php @@ -0,0 +1,292 @@ +resourceAssertion = new ResourceAssertionUser; + + $this->user = User::factory()->create([ + 'id' => $this->faker()->numberBetween(1, 100), + ]); + + $this->instance(UserCoreContract::class, $this->mock(UserCoreContract::class)); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $this->mock(LoggerMessageFormatterFactoryContract::class) + ); + Log::partialMock(); + } + + #[Test] + public function should_show_404_when_user_id_is_not_found() + { + // Arrange + $notFoundId = $this->user->id + 1; + + + // Act + $response = $this->deleteJson( + $this->getEndpointUrl($notFoundId), + ); + + + // Assert + $response->assertNotFound(); + } + + #[Test] + public function should_show_500_when_thrown_generic_error() + { + // Arrange + $exceptionMessage = new ExceptionMessageGeneric; + + $mockException = new Exception('generic error'); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($mockException) { + $mock->shouldReceive('get') + ->once() + ->withArgs(fn ( + GetUserPort $argInput + ) => $this->validateRequest($argInput)) + ->andThrow($mockException); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logErrorValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logErrorValue, + $mockException, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Show user endpoint', + [], + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingError( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + $mockException, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logErrorValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('error') + ->with($logErrorValue) + ->once(); + + + // Act + $response = $this->getJson( + $this->getEndpointUrl($this->user->id), + ); + + + // Assert + $response->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR); + $response->assertJsonPath('errors', $exceptionMessage->getJsonResponse()->toArray()); + } + + #[Test] + public function should_show_200_when_successfully_get_user_instance() + { + // Assert + /** @var User */ + $mockedUser = User::factory()->create(); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($mockedUser) { + $mock->shouldReceive('get') + ->once() + ->withArgs(fn ( + GetUserPort $argInput + ) => $this->validateRequest($argInput)) + ->andReturn($mockedUser); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logSuccessValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logSuccessValue, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Show user endpoint', + [], + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingSuccess( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Show user endpoint', + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logSuccessValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('info') + ->with($logSuccessValue) + ->once(); + + + // Act + $response = $this->getJson( + $this->getEndpointUrl($this->user->id), + ); + + + // Assert + $response->assertOk(); + $this->resourceAssertion->assertResource($this, $response); + } + + protected function getEndpointInfo(int $userId): string + { + return "GET {$this->getEndpointUrl($userId)}"; + } + + protected function getEndpointUrl(int $userId): string + { + return route('user.show', ['userID' => $userId]); + } + + protected function validateRequest(GetUserPort $argInput): bool + { + try { + $this->assertTrue($argInput->getUserModel()->is($this->user)); + return true; + } catch (Exception $e) { + dd($e); + } + } +} diff --git a/tests/Feature/User/UpdateUserTest.php b/tests/Feature/User/UpdateUserTest.php new file mode 100644 index 0000000..221f81d --- /dev/null +++ b/tests/Feature/User/UpdateUserTest.php @@ -0,0 +1,546 @@ +resourceAssertion = new ResourceAssertionUser; + + $this->user = User::factory()->create([ + 'id' => $this->faker()->numberBetween(1, 100), + ]); + + $this->instance(UserCoreContract::class, $this->mock(UserCoreContract::class)); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $this->mock(LoggerMessageFormatterFactoryContract::class) + ); + Log::partialMock(); + } + + #[Test] + public function should_show_404_when_user_id_is_not_found() + { + // Arrange + $notFoundId = $this->user->id + 1; + + + // Act + $response = $this->putJson( + $this->getEndpointUrl($notFoundId), + ); + + + // Assert + $response->assertNotFound(); + } + + #[Test] + #[DataProvider('invalidDataProvider')] + public function should_show_422_when_input_is_invalid( + string $errorMaker, + array $input, + ) { + // Act + $response = $this->putJson( + $this->getEndpointUrl($this->user->id), + $input, + ); + + + // Assert + $response->assertUnprocessable(); + $response->assertJsonValidationErrorFor($errorMaker, 'errors.meta'); + } + + public static function invalidDataProvider(): array + { + return [ + 'without email' => [ + 'email', + collect(self::validRequestInput()) + ->except('email') + ->toArray(), + ], + 'email is null' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => null, + ])->toArray(), + ], + 'email is empty string' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => '', + ])->toArray(), + ], + 'email is not in right format (now contain random string)' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => fake()->words(3, true), + ])->toArray(), + ], + 'email is not in right format (now contain array)' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => [fake()->words(3, true)], + ])->toArray(), + ], + 'email should be less than 250 (currently 251)' => [ + 'email', + collect(self::validRequestInput()) + ->replace([ + 'email' => fake()->regexify('[a-z]{241}@gmail.com'), + ])->toArray(), + ], + + 'without name' => [ + 'name', + collect(self::validRequestInput()) + ->except('name') + ->toArray(), + ], + 'name is null' => [ + 'name', + collect(self::validRequestInput()) + ->replace([ + 'name' => null, + ])->toArray(), + ], + 'name is empty string' => [ + 'name', + collect(self::validRequestInput()) + ->replace([ + 'name' => null, + ])->toArray(), + ], + 'name is more than 250 (currently 251)' => [ + 'name', + collect(self::validRequestInput()) + ->replace([ + 'name' => fake()->regexify('[a-z]{251}'), + ])->toArray(), + ], + + 'without password' => [ + 'password', + collect(self::validRequestInput()) + ->except('password') + ->toArray(), + ], + 'password is null' => [ + 'password', + collect(self::validRequestInput()) + ->replace([ + 'password' => null, + ])->toArray(), + ], + 'password is empty string' => [ + 'password', + collect(self::validRequestInput()) + ->replace([ + 'password' => null, + ])->toArray(), + ], + ]; + } + + #[Test] + public function should_show_409_when_thrown_duplicated_email_exception() + { + // Assert + $input = $this->validRequestInput(); + + $mockedExceptionResponse = collect(['foo' => 'bar']); + /** @var ExceptionMessage */ + $mockExceptionMessage = $this->mock( + ExceptionMessage::class, + function (MockInterface $mock) use ($mockedExceptionResponse) { + $mock->shouldReceive('getJsonResponse') + ->andReturn($mockedExceptionResponse); + $mock->shouldReceive('getMessage'); + } + ); + $mockException = new UserEmailDuplicatedException($mockExceptionMessage); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($mockException, $input) { + $mock->shouldReceive('update') + ->once() + ->withArgs(fn ( + UpdateUserPort $argInput + ) => $this->validateRequest($argInput, $input)) + ->andThrow($mockException); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logErrorValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logErrorValue, + $mockException, + $input, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Update user endpoint', + $input, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingError( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + $mockException, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logErrorValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('error') + ->with($logErrorValue) + ->once(); + + + // Act + $response = $this->putJson( + $this->getEndpointUrl($this->user->id), + $input, + ); + + + // Assert + $response->assertStatus(Response::HTTP_CONFLICT); + $response->assertJsonPath('errors', $mockedExceptionResponse->toArray()); + } + + #[Test] + public function should_show_500_when_generic_error_is_thrown() + { + // Assert + $input = $this->validRequestInput(); + $exceptionMessage = new ExceptionMessageGeneric; + + $mockException = new Exception('generic error'); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($input, $mockException) { + $mock->shouldReceive('update') + ->once() + ->withArgs(fn ( + UpdateUserPort $argInput + ) => $this->validateRequest($argInput, $input)) + ->andThrow($mockException); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logErrorValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logErrorValue, + $mockException, + $input, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Update user endpoint', + $input, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingError( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + $mockException, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logErrorValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('error') + ->with($logErrorValue) + ->once(); + + + // Act + $response = $this->putJson( + $this->getEndpointUrl($this->user->id), + $input + ); + + + // Assert + $response->assertStatus(Response::HTTP_INTERNAL_SERVER_ERROR); + $response->assertJsonPath( + 'errors', + $exceptionMessage->getJsonResponse()->toArray() + ); + } + + #[Test] + public function should_show_200_when_successfully_update_user() + { + // Assert + $input = $this->validRequestInput(); + /** @var User */ + $mockedUser = User::factory()->create(); + + $mockCore = $this->mock( + UserCoreContract::class, + function (MockInterface $mock) use ($input, $mockedUser) { + $mock->shouldReceive('update') + ->once() + ->withArgs(fn ( + UpdateUserPort $argInput + ) => $this->validateRequest($argInput, $input)) + ->andReturn($mockedUser); + } + ); + $this->instance(UserCoreContract::class, $mockCore); + + $logInfoValue = $this->faker->sentence; + $logSuccessValue = $this->faker->sentence; + + $mockLoggerFormatterFactory = $this->mock( + LoggerMessageFormatterFactoryContract::class, + function (MockInterface $mock) use ( + $logInfoValue, + $logSuccessValue, + $input, + ) { + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingBegin( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Update user endpoint', + $input, + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logInfoValue) + ) + ); + + $mock->shouldReceive('makeGeneric') + ->once() + ->withArgs(fn ( + string $argEndpoint, + string $argRequestID, + ProcessingStatus $argProcessingStatus, + string $argMessage, + array $argMeta, + ) => $this->validateLoggingSuccess( + $argEndpoint, + $argRequestID, + $argProcessingStatus, + $argMessage, + $argMeta, + $this->getEndpointInfo($this->user->id), + 'Update user endpoint', + ))->andReturn( + $this->mock( + LoggerMessageFormatter::class, + fn (MockInterface $mock) => $mock->shouldReceive('getMessage') + ->once()->andReturn($logSuccessValue) + ) + ); + } + ); + $this->instance( + LoggerMessageFormatterFactoryContract::class, + $mockLoggerFormatterFactory + ); + + Log::shouldReceive('info') + ->with($logInfoValue) + ->once(); + Log::shouldReceive('info') + ->with($logSuccessValue) + ->once(); + + + // Act + $response = $this->putJson( + $this->getEndpointUrl($this->user->id), + $input, + ); + + + // Assert + $response->assertStatus(Response::HTTP_CREATED); + $this->resourceAssertion->assertResource($this, $response); + } + + protected function getEndpointInfo(int $userId): string + { + return "PUT {$this->getEndpointUrl($userId)}"; + } + + protected function getEndpointUrl(int $userId): string + { + return route('user.update', ['userID' => $userId]); + } + + protected function validateRequest(UpdateUserPort $argInput, array $input): bool + { + try { + $this->assertSame($input['email'], $argInput->getEmail()); + $this->assertSame($input['name'], $argInput->getName()); + $this->assertSame($input['password'], $argInput->getUserPassword()); + $this->assertTrue($argInput->getUserModel()->is($this->user)); + return true; + } catch (Exception $e) { + dd($e); + } + } + + protected static function validRequestInput(): array + { + return [ + 'email' => 'faisal@budiono.com', + 'name' => 'faisal budiono', + 'password' => 'password', + ]; + } +} diff --git a/tests/Helper/Enum/TestBackedEnumInt.php b/tests/Helper/Enum/TestBackedEnumInt.php new file mode 100644 index 0000000..675dbe0 --- /dev/null +++ b/tests/Helper/Enum/TestBackedEnumInt.php @@ -0,0 +1,9 @@ + [ + OrderDirection::ASCENDING, + ], + 'desc' => [ + OrderDirection::DESCENDING, + ], + ]; + } +} diff --git a/tests/Helper/ResourceAssertion/ResourceAssertion.php b/tests/Helper/ResourceAssertion/ResourceAssertion.php new file mode 100644 index 0000000..72976c6 --- /dev/null +++ b/tests/Helper/ResourceAssertion/ResourceAssertion.php @@ -0,0 +1,11 @@ +assertJsonStructure([ + 'data' => [ + 'id', + 'name', + 'email', + 'created_at', + 'updated_at', + ], + ]); + } +} diff --git a/tests/Helper/ResourceAssertion/User/ResourceAssertionUserList.php b/tests/Helper/ResourceAssertion/User/ResourceAssertionUserList.php new file mode 100644 index 0000000..4b5c339 --- /dev/null +++ b/tests/Helper/ResourceAssertion/User/ResourceAssertionUserList.php @@ -0,0 +1,25 @@ +assertJsonStructure([ + 'data' => [ + '*' => [ + 'id', + 'name', + 'email', + 'created_at', + 'updated_at', + ] + ], + ]); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 2932d4a..03f580b 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -3,8 +3,21 @@ namespace Tests; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; +use Illuminate\Foundation\Testing\WithFaker; +use Illuminate\Support\Collection; +use ReflectionClass; +use ReflectionMethod; abstract class TestCase extends BaseTestCase { use CreatesApplication; + use WithFaker; + + protected function getClassMethods(string $classname): Collection + { + $reflection = new ReflectionClass($classname); + return collect($reflection->getMethods())->map(fn ( + ReflectionMethod $reflectionMethod + ) => $reflectionMethod->getName()); + } } diff --git a/tests/Unit/Core/Formatter/ExceptionMessage/ExceptionMessageGenericTest.php b/tests/Unit/Core/Formatter/ExceptionMessage/ExceptionMessageGenericTest.php new file mode 100644 index 0000000..4768fb7 --- /dev/null +++ b/tests/Unit/Core/Formatter/ExceptionMessage/ExceptionMessageGenericTest.php @@ -0,0 +1,55 @@ +assertInstanceOf(ExceptionMessage::class, $exceptionMessage); + } + + #[Test] + public function getJsonResponse_should_return_generic_json_response() + { + // Arrange + $exceptionMessage = new ExceptionMessageGeneric; + + + // Act + $result = $exceptionMessage->getJsonResponse(); + + + // Assert + $this->assertEquals(collect([ + 'message' => 'Something Wrong on Our Server', + 'errorCode' => ExceptionErrorCode::GENERIC->value, + 'meta' => [], + ]), $result); + } + + #[Test] + public function getMessage_should_return_generic_message() + { + // Arrange + $exceptionMessage = new ExceptionMessageGeneric; + + + // Act + $result = $exceptionMessage->getMessage(); + + + // Assert + $this->assertEquals('Something Wrong on Our Server', $result); + } +} diff --git a/tests/Unit/Core/Formatter/ExceptionMessage/ExceptionMessageStandardTest.php b/tests/Unit/Core/Formatter/ExceptionMessage/ExceptionMessageStandardTest.php new file mode 100644 index 0000000..cbb61b6 --- /dev/null +++ b/tests/Unit/Core/Formatter/ExceptionMessage/ExceptionMessageStandardTest.php @@ -0,0 +1,89 @@ +assertInstanceOf(ExceptionMessage::class, $exceptionMessage); + } + + #[Test] + public function getJsonResponse_should_return_formatted_json_response_when_constructor_is_completely_filled() + { + // Arrange + $message = $this->faker->words(3, true); + $errorCode = $this->faker->word(); + $meta = Collection::times(4)->mapWithKeys(fn () => [ + $this->faker->word => $this->faker->words(), + ])->toArray(); + + + // Act + $exceptionMessage = new ExceptionMessageStandard( + $message, + $errorCode, + $meta, + ); + $result = $exceptionMessage->getJsonResponse(); + + + // Assert + $this->assertEqualsCanonicalizing(collect([ + 'message' => $message, + 'errorCode' => $errorCode, + 'meta' => $meta, + ]), $result); + } + + #[Test] + public function getJsonResponse_should_return_formatted_json_response_when_created_without_meta() + { + // Arrange + $message = $this->faker->words(3, true); + $errorCode = $this->faker->word(); + + + // Act + $exceptionMessage = new ExceptionMessageStandard( + $message, + $errorCode, + ); + $result = $exceptionMessage->getJsonResponse(); + + + // Assert + $this->assertEqualsCanonicalizing(collect([ + 'message' => $message, + 'errorCode' => $errorCode, + 'meta' => [], + ]), $result); + } + + #[Test] + public function getMessage_should_return_message() + { + // Arrange + $message = $this->faker->words(3, true); + + + // Act + $exceptionMessage = new ExceptionMessageStandard($message, 'some-const'); + $result = $exceptionMessage->getMessage(); + + + // Assert + $this->assertSame($message, $result); + } +} diff --git a/tests/Unit/Core/Formatter/Randomizer/RandomizerUUIDTest.php b/tests/Unit/Core/Formatter/Randomizer/RandomizerUUIDTest.php new file mode 100644 index 0000000..f136086 --- /dev/null +++ b/tests/Unit/Core/Formatter/Randomizer/RandomizerUUIDTest.php @@ -0,0 +1,39 @@ +assertInstanceOf(Randomizer::class, $this->makeService()); + } + + #[Test] + public function getRandomizeString_should_randomized_uuid() + { + // Arrange + $service = $this->makeService(); + + + // Act + $result = $service->getRandomizeString(); + + + // Assert + $this->assertTrue(Str::isUuid($result)); + } + + protected function makeService(): RandomizerUUID + { + return new RandomizerUUID; + } +} diff --git a/tests/Unit/Core/Logger/MessageFormatter/LoggerMessageFormatterFactoryTest.php b/tests/Unit/Core/Logger/MessageFormatter/LoggerMessageFormatterFactoryTest.php new file mode 100644 index 0000000..0da1599 --- /dev/null +++ b/tests/Unit/Core/Logger/MessageFormatter/LoggerMessageFormatterFactoryTest.php @@ -0,0 +1,62 @@ +assertInstanceOf( + LoggerMessageFormatterFactoryContract::class, + $this->makeService() + ); + } + + #[Test] + public function makeGeneric_should_return_generic_message_formatter() + { + // Arrange + $endpoint = $this->faker->sentence(); + $requestID = $this->faker->sentence(); + $processingStatus = $this->faker->randomElement(ProcessingStatus::cases()); + $message = $this->faker->sentence(); + $meta = $this->faker->sentences(); + + $service = $this->makeService(); + + + // Act + $result = $service->makeGeneric( + $endpoint, + $requestID, + $processingStatus, + $message, + $meta, + ); + + + // Assert + $expectedResult = new LoggerMessageFormatterGeneric( + $endpoint, + $requestID, + $processingStatus, + $message, + $meta, + ); + $this->assertEquals($expectedResult, $result); + } + + protected function makeService(): LoggerMessageFormatterFactory + { + return new LoggerMessageFormatterFactory; + } +} diff --git a/tests/Unit/Core/Logger/MessageFormatter/LoggerMessageFormatterGenericTest.php b/tests/Unit/Core/Logger/MessageFormatter/LoggerMessageFormatterGenericTest.php new file mode 100644 index 0000000..2ed2181 --- /dev/null +++ b/tests/Unit/Core/Logger/MessageFormatter/LoggerMessageFormatterGenericTest.php @@ -0,0 +1,74 @@ +assertInstanceOf(LoggerMessageFormatter::class, $service); + } + + #[Test] + public function should_format_logging_message_to_string_json_with_complete_data() + { + // Arrange + $endpoint = $this->faker()->sentence; + $requestId = $this->faker()->uuid; + /** @var ProcessingStatus */ + $processingStatus = $this->faker()->randomElement(ProcessingStatus::cases()); + $message = $this->faker()->sentence; + $meta = $this->faker()->sentences(); + + $service = new LoggerMessageFormatterGeneric( + $endpoint, + $requestId, + $processingStatus, + $message, + $meta, + ); + + + // Act + $result = $service->getMessage(); + + + // Assert + $this->assertJson($result); + + $arrayedResult = json_decode($result, true); + + $this->assertArrayHasKey('endpoint', $arrayedResult); + $this->assertSame($endpoint, $arrayedResult['endpoint']); + + $this->assertArrayHasKey('request-id', $arrayedResult); + $this->assertSame($requestId, $arrayedResult['request-id']); + + $this->assertArrayHasKey('processing-status', $arrayedResult); + $this->assertSame($processingStatus->value, $arrayedResult['processing-status']); + + $this->assertArrayHasKey('message', $arrayedResult); + $this->assertSame($message, $arrayedResult['message']); + + $this->assertArrayHasKey('meta', $arrayedResult); + $this->assertSame($meta, $arrayedResult['meta']); + } +} diff --git a/tests/Unit/Core/User/UserCore/CreateUserCoreTest.php b/tests/Unit/Core/User/UserCore/CreateUserCoreTest.php new file mode 100644 index 0000000..709a2d3 --- /dev/null +++ b/tests/Unit/Core/User/UserCore/CreateUserCoreTest.php @@ -0,0 +1,105 @@ +core = new UserCore(); + + $this->mockRequest = $this->mock(CreateUserPort::class, function (MockInterface $mock) { + $this->getClassMethods(CreateUserPort::class)->each( + fn (string $methodName) => + $this->mockedRequestMethods[$methodName] = $mock->shouldReceive($methodName) + ); + }); + } + + #[Test] + public function should_implement_user_core_contract() + { + // Assert + $this->assertInstanceOf(UserCoreContract::class, $this->core); + } + + #[Test] + public function should_successfully_save_and_return_user_data() + { + // Arrange + $name = $this->faker->name; + $email = $this->faker->email; + $password = $this->faker->password; + + $this->mockedRequestMethods['getName']->andReturn($name); + $this->mockedRequestMethods['getEmail']->andReturn($email); + $this->mockedRequestMethods['getUserPassword']->andReturn($password); + + $hashedPassword = $this->faker->words(7, true); + Hash::shouldReceive('make')->with($password)->andReturn($hashedPassword); + + + // Act + $result = $this->core->create($this->mockRequest); + + + // Assert + $this->assertDatabaseCount('users', 1); + + $expectedResult = [ + 'name' => $name, + 'email' => $email, + 'password' => $hashedPassword, + ]; + $this->assertDatabaseHas('users', $expectedResult); + $this->assertDatabaseHas($result, $expectedResult); + } + + #[Test] + public function should_throw_error_when_user_email_already_registered() + { + // Arrange + $email = $this->faker->email(); + + User::factory()->create([ + 'email' => $email, + ]); + + $this->mockedRequestMethods['getEmail']->andReturn($email); + + + // Assert + $expectedException = new UserEmailDuplicatedException(new ExceptionMessageStandard( + 'Email is duplicated', + UserExceptionCode::DUPLICATED->value, + )); + $this->expectExceptionObject($expectedException); + + + // Act + $this->core->create($this->mockRequest); + } +} diff --git a/tests/Unit/Core/User/UserCore/DeleteUserCoreTest.php b/tests/Unit/Core/User/UserCore/DeleteUserCoreTest.php new file mode 100644 index 0000000..73bcba3 --- /dev/null +++ b/tests/Unit/Core/User/UserCore/DeleteUserCoreTest.php @@ -0,0 +1,67 @@ +core = new UserCore(); + + $this->mockRequest = $this->mock(DeleteUserPort::class, function (MockInterface $mock) { + $this->getClassMethods(DeleteUserPort::class)->each( + fn (string $methodName) => + $this->mockedRequestMethods[$methodName] = $mock->shouldReceive($methodName) + ); + }); + } + + #[Test] + public function should_implement_right_interface() + { + // Assert + $this->assertInstanceOf(UserCoreContract::class, $this->core); + } + + #[Test] + public function should_successfully_soft_delete_requested_user() + { + // Arrange + $totalData = 5; + User::factory()->count($totalData)->create(); + + /** @var User */ + $user = User::find($this->faker()->numberBetween(1, User::count())); + + $this->mockedRequestMethods['getUserModel']->once()->withNoArgs() + ->andReturn($user); + + + // Act + $this->core->delete($this->mockRequest); + + + // Assert + $this->assertSoftDeleted($user); + $this->assertDatabaseCount('users', $totalData); + } +} diff --git a/tests/Unit/Core/User/UserCore/GetAllUserCoreTest.php b/tests/Unit/Core/User/UserCore/GetAllUserCoreTest.php new file mode 100644 index 0000000..b222592 --- /dev/null +++ b/tests/Unit/Core/User/UserCore/GetAllUserCoreTest.php @@ -0,0 +1,253 @@ +core = new UserCore(); + + $this->mockRequest = $this->mock(GetAllUserPort::class, function (MockInterface $mock) { + $this->getClassMethods(GetAllUserPort::class)->each( + function (string $methodName) use ($mock) { + $this->mockedRequestMethods[$methodName] = $mock->shouldReceive($methodName); + } + ); + }); + } + + #[Test] + public function should_implement_right_interface() + { + // Assert + $this->assertInstanceOf(UserCoreContract::class, $this->core); + } + + #[Test] + public function should_return_30_latest_data_when_called_with_no_parameter() + { + // Assert + $this->makeMockUsers(35); + + $this->mockedRequestMethods['getPerPage'] + ->once() + ->withNoArgs() + ->andReturnNull(); + + + // Act + $results = $this->core->getAll($this->mockRequest); + + + // Assert + $expectedResults = DB::table('users') + ->orderByDesc('created_at') + ->limit(30) + ->get(); + + $this->assertSame($expectedResults->count(), $results->perPage()); + $this->assertSame(1, $results->currentPage()); + + collect($results->items())->each(function ( + User $user, + int $index + ) use ($expectedResults) { + $this->assertSame($expectedResults[$index]->id, $user->id); + }); + } + + #[Test] + public function should_return_data_with_requested_per_page() + { + // Assert + $this->makeMockUsers(12); + + $perPage = 10; + $this->mockedRequestMethods['getPerPage'] + ->once() + ->withNoArgs() + ->andReturn($perPage); + + + // Act + $results = $this->core->getAll($this->mockRequest); + + + // Assert + $expectedResults = DB::table('users') + ->orderByDesc('created_at') + ->limit($perPage) + ->get(); + + $this->assertSame($perPage, $results->perPage()); + $this->assertSame(1, $results->currentPage()); + + collect($results->items())->each(function ( + User $user, + int $index + ) use ($expectedResults) { + $this->assertSame($expectedResults[$index]->id, $user->id); + }); + } + + #[Test] + public function should_return_data_with_requested_page() + { + // Assert + $this->makeMockUsers(65); + + $defaultPerPage = 30; + $page = 3; + $this->mockedRequestMethods['getPage'] + ->once() + ->withNoArgs() + ->andReturn($page); + + + // Act + $results = $this->core->getAll($this->mockRequest); + + + // Assert + $expectedResults = DB::table('users') + ->orderByDesc('created_at') + ->limit($defaultPerPage) + ->offset(60) + ->get(); + + $this->assertSame($defaultPerPage, $results->perPage()); + $this->assertSame($page, $results->currentPage()); + + collect($results->items())->each(function ( + User $user, + int $index + ) use ($expectedResults) { + $this->assertSame($expectedResults[$index]->id, $user->id); + }); + } + + #[Test] + #[DataProviderExternal(QueryDataProvider::class, 'orderDirection')] + public function should_return_data_with_requested_order_direction( + OrderDirection $orderDirection + ) { + // Assert + $this->makeMockUsers(30); + + $defaultPerPage = 30; + + $this->mockedRequestMethods['getOrderDirection'] + ->once() + ->withNoArgs() + ->andReturn($orderDirection); + + + // Act + $results = $this->core->getAll($this->mockRequest); + + + // Assert + $expectedResults = DB::table('users') + ->select('id') + ->orderBy('created_at', $orderDirection->value) + ->limit($defaultPerPage) + ->get(); + + $this->assertSame($defaultPerPage, $results->perPage()); + $this->assertSame(1, $results->currentPage()); + + collect($results->items())->each(function ( + User $user, + int $index + ) use ($expectedResults) { + $this->assertSame($expectedResults[$index]->id, $user->id); + }); + } + + #[Test] + #[DataProvider('orderByDataProvider')] + public function should_return_data_with_requested_order_by( + UserOrderBy $orderBy + ) { + // Assert + $this->makeMockUsers(30); + + $defaultPerPage = 30; + + $this->mockedRequestMethods['getOrderBy'] + ->once() + ->withNoArgs() + ->andReturn($orderBy); + + + // Act + $results = $this->core->getAll($this->mockRequest); + + + // Assert + $expectedResults = DB::table('users') + ->select('id') + ->orderBy($orderBy->value, 'desc') + ->limit($defaultPerPage) + ->get(); + + $this->assertSame($defaultPerPage, $results->perPage()); + $this->assertSame(1, $results->currentPage()); + + collect($results->items())->each(function ( + User $user, + int $index + ) use ($expectedResults) { + $this->assertSame($expectedResults[$index]->id, $user->id); + }); + } + + public static function orderByDataProvider(): array + { + return [ + 'by name' => [ + UserOrderBy::NAME, + ], + 'by email' => [ + UserOrderBy::EMAIL, + ], + 'by created_at' => [ + UserOrderBy::CREATED_AT, + ], + ]; + } + + protected function makeMockUsers(int $numberOfData): void + { + User::factory()->count($numberOfData)->create()->each(function (User $user) { + $user->created_at = now()->addMinutes($user->id); + $user->save(); + }); + } +} diff --git a/tests/Unit/Core/User/UserCore/GetUserCoreTest.php b/tests/Unit/Core/User/UserCore/GetUserCoreTest.php new file mode 100644 index 0000000..a5d0e5d --- /dev/null +++ b/tests/Unit/Core/User/UserCore/GetUserCoreTest.php @@ -0,0 +1,70 @@ +core = new UserCore(); + + $this->mockRequest = $this->mock(GetUserPort::class, function (MockInterface $mock) { + $this->getClassMethods(GetUserPort::class)->each( + fn (string $methodName) => + $this->mockedRequestMethods[$methodName] = $mock->shouldReceive($methodName) + ); + }); + } + + #[Test] + public function should_implement_right_interface() + { + // Assert + $this->assertInstanceOf(UserCoreContract::class, $this->core); + } + + #[Test] + public function should_return_user_model() + { + // Assert + User::factory()->count(5)->create(); + + /** @var User */ + $user = User::find($this->faker->numberBetween(1, User::count())); + + $this->mockedRequestMethods['getUserModel'] + ->once() + ->withNoArgs() + ->andReturn($user); + + + // Act + $result = $this->core->get($this->mockRequest); + + + // Assert + $this->assertEquals( + $user->replicate()->refresh(), + $result->replicate()->refresh() + ); + } +} diff --git a/tests/Unit/Core/User/UserCore/UpdateUserCoreTest.php b/tests/Unit/Core/User/UserCore/UpdateUserCoreTest.php new file mode 100644 index 0000000..4f4bd0e --- /dev/null +++ b/tests/Unit/Core/User/UserCore/UpdateUserCoreTest.php @@ -0,0 +1,167 @@ +core = new UserCore(); + + $this->mockRequest = $this->mock(UpdateUserPort::class, function (MockInterface $mock) { + $this->getClassMethods(UpdateUserPort::class)->each( + fn (string $methodName) => + $this->mockedRequestMethods[$methodName] = $mock->shouldReceive($methodName) + ); + }); + } + + #[Test] + public function should_implement_user_core_contract() + { + // Assert + $this->assertInstanceOf(UserCoreContract::class, $this->core); + } + + #[Test] + public function should_throw_user_email_duplicated_exception_when_email_is_used_by_other_user() + { + // Assert + User::factory()->count(5)->create(); + /** @var User */ + $duplicatedUser = User::find(1); + + /** @var User */ + $user = User::find($this->faker()->numberBetween(2, User::count())); + $this->mockedRequestMethods['getUserModel']->once()->withNoArgs() + ->andReturn($user); + + $this->mockedRequestMethods['getEmail']->once()->withNoArgs() + ->andReturn($duplicatedUser->email); + + + // Assert + $expectedException = new UserEmailDuplicatedException(new ExceptionMessageStandard( + 'Email is already in used', + UserExceptionCode::DUPLICATED->value, + )); + $this->expectExceptionObject($expectedException); + + + // Act + $result = $this->core->update($this->mockRequest); + + + // Assert + $this->assertDatabaseHas('users', [ + 'id' => $result->id, + 'name' => $user->name, + 'email' => $user->name, + 'password' => $user->password, + ]); + } + + #[Test] + public function should_update_user_detail_successfully() + { + // Assert + User::factory()->count(5)->create(); + + /** @var User */ + $user = User::find($this->faker()->numberBetween(1, User::count())); + $this->mockedRequestMethods['getUserModel']->once()->withNoArgs() + ->andReturn($user); + + $email = $this->faker()->email(); + $this->mockedRequestMethods['getEmail']->once()->withNoArgs() + ->andReturn($email); + + $name = $this->faker()->name(); + $this->mockedRequestMethods['getName']->once()->withNoArgs() + ->andReturn($name); + + $password = $this->faker()->words(3, true); + $this->mockedRequestMethods['getUserPassword']->once()->withNoArgs() + ->andReturn($password); + + $mockedPassword = $this->faker()->words(4, true); + Hash::shouldReceive('make')->with($password)->once() + ->andReturn($mockedPassword); + + + // Act + $result = $this->core->update($this->mockRequest); + + + // Assert + $this->assertDatabaseHas('users', [ + 'id' => $result->id, + 'name' => $name, + 'email' => $email, + 'password' => $mockedPassword, + ]); + } + + #[Test] + public function should_update_user_detail_successfully_even_when_email_is_not_changed() + { + // Assert + User::factory()->count(5)->create(); + + /** @var User */ + $user = User::find($this->faker()->numberBetween(1, User::count())); + $this->mockedRequestMethods['getUserModel']->once()->withNoArgs() + ->andReturn($user); + + $this->mockedRequestMethods['getEmail']->once()->withNoArgs() + ->andReturn($user->email); + + $name = $this->faker()->name(); + $this->mockedRequestMethods['getName']->once()->withNoArgs() + ->andReturn($name); + + $password = $this->faker()->words(3, true); + $this->mockedRequestMethods['getUserPassword']->once()->withNoArgs() + ->andReturn($password); + + $mockedPassword = $this->faker()->words(4, true); + Hash::shouldReceive('make')->with($password)->once() + ->andReturn($mockedPassword); + + + // Act + $result = $this->core->update($this->mockRequest); + + + // Assert + $this->assertDatabaseHas('users', [ + 'id' => $result->id, + 'name' => $name, + 'email' => $user->email, + 'password' => $mockedPassword, + ]); + } +} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index e5c5fef..0000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,18 +0,0 @@ -assertTrue(true); - } -} diff --git a/tests/Unit/Exception/Http/ConflictExceptionTest.php b/tests/Unit/Exception/Http/ConflictExceptionTest.php new file mode 100644 index 0000000..3d782f7 --- /dev/null +++ b/tests/Unit/Exception/Http/ConflictExceptionTest.php @@ -0,0 +1,19 @@ + [null], + 'Exception' => [new Exception('some error')], + 'RuntimeException' => [new RuntimeException('some error')], + ]; + } +} diff --git a/tests/Unit/Exception/Http/ForbiddenExceptionTest.php b/tests/Unit/Exception/Http/ForbiddenExceptionTest.php new file mode 100644 index 0000000..76ac02d --- /dev/null +++ b/tests/Unit/Exception/Http/ForbiddenExceptionTest.php @@ -0,0 +1,19 @@ +mockedMessage = 'some error message'; + $this->mockedJsonResponse = collect([ + 'message' => 'something', + ]); + + $this->mockExceptionMessage = $this->mock(ExceptionMessage::class, function ( + MockInterface $mock + ) { + $mock->shouldReceive('getMessage')->andReturn($this->mockedMessage); + $mock->shouldReceive('getJsonResponse')->andReturn($this->mockedJsonResponse); + }); + } + + #[Test] + public function getCode_should_return_right_json_content() + { + // Act + $exception = new ($this->makeHttpException())($this->mockExceptionMessage); + $result = $exception->getCode(); + + + // Assert + $this->assertEquals($this->getHttpStatusCode(), $result); + } + + #[Test] + public function getMessage_should_return_right_json_content() + { + // Act + $exception = new ($this->makeHttpException())($this->mockExceptionMessage); + $result = $exception->getMessage(); + + + // Assert + $this->assertEquals($this->mockedMessage, $result); + } + + #[Test] + #[DataProviderExternal(DataProvider::class, 'previousExceptions')] + public function getPrevious_should_return_right_json_content(?Exception $mockPreviousException) + { + // Act + $exception = new ($this->makeHttpException())( + $this->mockExceptionMessage, + $mockPreviousException + ); + $result = $exception->getPrevious(); + + + // Assert + $this->assertEquals($mockPreviousException, $result); + } + + #[Test] + public function render_should_return_right_json_content() + { + // Act + $exception = new ($this->makeHttpException())($this->mockExceptionMessage); + $result = $exception->render(''); + + + // Assert + $this->assertEqualsCanonicalizing( + [ + 'errors' => $this->mockedJsonResponse->toArray(), + ], + json_decode($result->content(), true) + ); + } +} diff --git a/tests/Unit/Exception/Http/InternalServerErrorExceptionTest.php b/tests/Unit/Exception/Http/InternalServerErrorExceptionTest.php new file mode 100644 index 0000000..b2cdd63 --- /dev/null +++ b/tests/Unit/Exception/Http/InternalServerErrorExceptionTest.php @@ -0,0 +1,19 @@ +faker()->uuid; + + /** @var Randomizer */ + $mockRandomizer = $this->mock( + Randomizer::class, + function (MockInterface $mock) use ($mockedRandomString) { + $mock->shouldReceive('getRandomizeString') + ->once() + ->andReturn($mockedRandomString); + } + ); + + $mockRequest = new Request(); + $middleware = new XRequestIDMiddleware($mockRandomizer); + + + // Act + $response = $middleware->handle( + $mockRequest, + function (Request $argRequest) use ($mockedRandomString, $headerName) { + $this->assertSame( + $mockedRandomString, + $argRequest->header($headerName) + ); + return new Response(); + } + ); + + + // Assert + $this->assertSame( + $mockedRandomString, + $response->headers->get($headerName) + ); + } +} diff --git a/tests/Unit/Http/Resource/User/UserResourceTest.php b/tests/Unit/Http/Resource/User/UserResourceTest.php new file mode 100644 index 0000000..f7df940 --- /dev/null +++ b/tests/Unit/Http/Resource/User/UserResourceTest.php @@ -0,0 +1,62 @@ +create(); + + + // Act + $result = json_decode(UserResource::make($user)->toJson(), true); + + + // Assert + $this->assertEquals([ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'created_at' => $user->created_at->format(DatetimeFormat::ISO_WITH_MILLIS->value), + 'updated_at' => $user->updated_at->format(DatetimeFormat::ISO_WITH_MILLIS->value), + ], $result); + } + + #[Test] + public function should_return_right_arrayable_format_when_nullable_date_is_null() + { + // Arrange + /** @var User */ + $user = User::factory()->create([ + 'created_at' => null, + 'updated_at' => null, + ]); + + + // Act + $result = json_decode(UserResource::make($user)->toJson(), true); + + + // Assert + $this->assertEquals([ + 'id' => $user->id, + 'name' => $user->name, + 'email' => $user->email, + 'created_at' => null, + 'updated_at' => null, + ], $result); + } +} diff --git a/tests/Unit/Models/User/UserFindByIdOrFailTest.php b/tests/Unit/Models/User/UserFindByIdOrFailTest.php new file mode 100644 index 0000000..47e9c6e --- /dev/null +++ b/tests/Unit/Models/User/UserFindByIdOrFailTest.php @@ -0,0 +1,53 @@ +user = User::factory()->create([ + 'id' => $this->faker()->numberBetween(1, 100), + ]); + } + + #[Test] + public function should_successfully_return_user_instance() + { + // Act + $user = User::findByIdOrFail($this->user->id); + + + // Assert + $this->assertTrue($user->is($this->user)); + } + + #[Test] + public function should_throw_model_not_found_exception_when_id_is_not_found() + { + // Assert + $expectedException = new ModelNotFoundException(new ExceptionMessageStandard( + 'User ID is not found', + ExceptionErrorCode::MODEL_NOT_FOUND->value, + )); + $this->expectExceptionObject($expectedException); + + + // Act + User::findByIdOrFail(1000); + } +} diff --git a/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseAbstract.php b/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseAbstract.php new file mode 100644 index 0000000..500732f --- /dev/null +++ b/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseAbstract.php @@ -0,0 +1,65 @@ +test = $test; + } + + public function assertBind(): void + { + $abstractWithImplementClassNames = $this->abstractWithImplementationList(); + + foreach ($abstractWithImplementClassNames as $abstractClassName => $implementorSpecs) { + $this->test->applicationMock->shouldReceive('bind') + ->once() + ->with($abstractClassName, Mockery::on(function ($app) use ($implementorSpecs) { + $implementorClassName = $implementorSpecs[0]; + $constructorClassNames = $implementorSpecs[1] ?? []; + + $constructorArgs = collect($constructorClassNames)->map( + fn ($className) => $this->test->mock($className) + ); + + $service = new $implementorClassName(...$constructorArgs); + + $this->test->assertEquals($service, $app($this->test->applicationMock)); + + return true; + }))->andReturnNull(); + } + } + + public function assertMake(): void + { + $abstractClassNames = array_keys($this->abstractWithImplementationList()); + + foreach ($abstractClassNames as $className) { + $this->test->applicationMock->shouldReceive('make') + ->with(Mockery::on(function (string $argClassName) use ($className) { + try { + $this->test->assertSame($className, $argClassName); + + return true; + } catch (\Throwable $th) { + return false; + } + }))->andReturn($this->test->mock($className)); + } + } + + abstract protected function abstractWithImplementationList(): array; +} diff --git a/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseFormatter.php b/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseFormatter.php new file mode 100644 index 0000000..9d1d394 --- /dev/null +++ b/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseFormatter.php @@ -0,0 +1,18 @@ + [ + RandomizerUUID::class, + ], + ]; + } +} diff --git a/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseLogger.php b/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseLogger.php new file mode 100644 index 0000000..f035bbc --- /dev/null +++ b/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseLogger.php @@ -0,0 +1,18 @@ + [ + LoggerMessageFormatterFactory::class, + ], + ]; + } +} diff --git a/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseUser.php b/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseUser.php new file mode 100644 index 0000000..a772217 --- /dev/null +++ b/tests/Unit/Providers/CoreBinder/CoreBinderTestCaseUser.php @@ -0,0 +1,18 @@ + [ + UserCore::class, + ], + ]; + } +} diff --git a/tests/Unit/Providers/CoreServiceProviderTest.php b/tests/Unit/Providers/CoreServiceProviderTest.php new file mode 100644 index 0000000..394cc51 --- /dev/null +++ b/tests/Unit/Providers/CoreServiceProviderTest.php @@ -0,0 +1,62 @@ +applicationMock = Mockery::mock(Application::class); + $this->serviceProvider = new CoreServiceProvider($this->applicationMock); + } + + #[Test] + public function should_be_able_to_be_contructed() + { + // Assert + $this->assertInstanceOf(ServiceProvider::class, $this->serviceProvider); + } + + #[Test] + public function should_bind_core_service() + { + // Arrange + $coreAssertionClassNames = [ + CoreBinderTestCaseFormatter::class, + CoreBinderTestCaseLogger::class, + CoreBinderTestCaseUser::class, + ]; + + + // Assert + foreach ($coreAssertionClassNames as $className) { + /** @var CoreBinderTestCaseAbstract */ + $coreAssertion = new $className($this); + + $coreAssertion->assertBind(); + $coreAssertion->assertMake(); + } + + + // Act + $this->serviceProvider->boot(); + } +} diff --git a/tests/Unit/Providers/ModelBinding/ModelBindingUserTest.php b/tests/Unit/Providers/ModelBinding/ModelBindingUserTest.php new file mode 100644 index 0000000..1503579 --- /dev/null +++ b/tests/Unit/Providers/ModelBinding/ModelBindingUserTest.php @@ -0,0 +1,87 @@ +user = User::factory()->create([ + 'id' => $this->faker->numberBetween(1, 100), + ]); + + Route::get('/tests/{userID}', function (User $userID) { + return response()->json([ + 'foo' => $userID->id, + ]); + })->middleware('model-binding')->name('dummy'); + } + + #[Test] + public function should_show_200_when_user_id_found() + { + // Act + $response = $this->getJson( + '/tests/' . $this->user->id, + ); + + + // Assert + $response->assertOk(); + $response->assertJson([ + 'foo' => $this->user->id, + ]); + } + + #[Test] + public function should_show_404_when_user_id_not_found() + { + // Act + $response = $this->getJson( + '/tests/' . $this->user->id + 1, + ); + + + // Assert + $response->assertNotFound(); + + $expectedErrorMessage = new ExceptionMessageStandard( + 'User ID is not found', + ExceptionErrorCode::MODEL_NOT_FOUND->value, + ); + $response->assertJsonPath( + 'errors', + $expectedErrorMessage->getJsonResponse()->toArray() + ); + } + + #[Test] + public function should_not_register_route_when_model_binding_is_not_number() + { + // Arrange + $this->withoutExceptionHandling(); + + $this->expectException(NotFoundHttpException::class); + + + // Act + $this->getJson( + '/tests/asd', + ); + } +} diff --git a/tests/Unit/Rules/Date/DateISORuleTest.php b/tests/Unit/Rules/Date/DateISORuleTest.php new file mode 100644 index 0000000..6e828eb --- /dev/null +++ b/tests/Unit/Rules/Date/DateISORuleTest.php @@ -0,0 +1,84 @@ +assertInstanceOf(ValidationRule::class, $this->makeService()); + } + + #[Test] + #[DataProvider('invalidDataProvider')] + public function should_called_fail_closure_when_value_is_not_valid(mixed $input) + { + // Arrange + $service = $this->makeService(); + + + // Act + $service->validate('', $input, function ($argMessage) { + $this->assertSame( + 'The :attribute should be ISO formatted datetime in UTC timezone with millisecond', + $argMessage + ); + }); + + + // Assert + $this->assertSame(1, $this->getCount()); + } + + public static function invalidDataProvider(): array + { + return [ + 'random string' => ['some random string'], + 'some array' => [['some random string']], + 'datetime in SQL format' => ['2022-03-31 20:00:00'], + 'datetime ISO without milliseconds' => ['2022-03-31T20:00:00Z'], + 'datetime ISO but the time is not valid' => ['2022-03-31T25:00:00.0Z'], + ]; + } + + #[Test] + #[DataProvider('validDataProvider')] + public function should_NOT_call_fail_closure_when_value_has_valid_format(mixed $input) + { + // Arrange + $service = $this->makeService(); + + + // Act + $service->validate('', $input, function ($argMessage) { + $this->fail(); + }); + + + // Assert + $this->expectNotToPerformAssertions(); + } + + public static function validDataProvider(): array + { + return [ + 'in UTC' => ['2022-03-31T20:00:00.0Z'], + 'have timezone other than UTC (currently Asia/Jakarta)' => ['2022-03-31T20:00:00.0+07:00'], + ]; + } + + protected function makeService(): DateISORule + { + return new DateISORule; + } +} diff --git a/tests/Unit/Rules/Enum/BackedEnumRuleTest.php b/tests/Unit/Rules/Enum/BackedEnumRuleTest.php new file mode 100644 index 0000000..03e096b --- /dev/null +++ b/tests/Unit/Rules/Enum/BackedEnumRuleTest.php @@ -0,0 +1,117 @@ +assertInstanceOf(ValidationRule::class, $service); + } + + #[Test] + #[DataProvider('invalidEnumDataProvider')] + public function should_called_fail_when_key_in_by_invalid_enum( + BackedEnum $backedEnum, + string $errorMessage, + string|int $mockedInput, + ) { + // Arrange + $service = new BackedEnumRule($backedEnum); + + + // Act + $service->validate('', $mockedInput, function (mixed $argMessage) use ($errorMessage) { + $this->assertSame($errorMessage, $argMessage); + }); + + + // Assert + $this->assertSame(1, $this->getCount()); + } + + public static function invalidEnumDataProvider(): array + { + return [ + 'backed enum string with string value' => [ + TestBackedEnumString::FOO, + 'The :attribute should only contain: foo, bar', + 'random', + ], + 'backed enum string with int value' => [ + TestBackedEnumString::FOO, + 'The :attribute should only contain: foo, bar', + 100, + ], + + 'backed enum int with string value' => [ + TestBackedEnumInt::FOO, + 'The :attribute should only contain: 1, 102', + 'random', + ], + 'backed enum int with string int' => [ + TestBackedEnumInt::FOO, + 'The :attribute should only contain: 1, 102', + 100, + ], + ]; + } + + #[Test] + #[DataProvider('validEnumDataProvider')] + public function should_NOT_call_fail_closure_when_value_is_valid_enum_value( + BackedEnum $backedEnum, + mixed $value, + ) { + // Arrange + $service = new BackedEnumRule($backedEnum); + + + // Act + $service->validate('', $value, function (mixed $argMessage) { + $this->fail(); + }); + + + // Assert + $this->expectNotToPerformAssertions(); + } + + public static function validEnumDataProvider(): array + { + return [ + 'backed enum string #1' => [ + TestBackedEnumString::FOO, + 'bar', + ], + 'backed enum string #2' => [ + TestBackedEnumString::FOO, + TestBackedEnumString::FOO->value, + ], + + 'backed enum int #1' => [ + TestBackedEnumInt::FOO, + 102, + ], + 'backed enum int #2' => [ + TestBackedEnumInt::FOO, + TestBackedEnumInt::FOO->value, + ], + ]; + } +}