diff --git a/backend/.gitignore b/backend/.gitignore index c15afe2..a725465 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1 @@ -vendor/ -.phpunit.cache/ +vendor/ \ No newline at end of file diff --git a/backend/app/Auth/BcryptPasswordHasher.php b/backend/app/Auth/BcryptPasswordHasher.php deleted file mode 100644 index 0bc4a46..0000000 --- a/backend/app/Auth/BcryptPasswordHasher.php +++ /dev/null @@ -1,16 +0,0 @@ -token; - } - - public function getUser(): User - { - return $this->user; - } - - public function getCreatedAt(): DateTimeImmutable - { - return $this->createdAt; - } - - public function getExpiresAt(): DateTimeImmutable - { - return $this->expiresAt; - } - - public function isExpired(DateTimeImmutable $now): bool - { - return $now >= $this->expiresAt; - } -} diff --git a/backend/app/Auth/SessionRepository.php b/backend/app/Auth/SessionRepository.php deleted file mode 100644 index cabae60..0000000 --- a/backend/app/Auth/SessionRepository.php +++ /dev/null @@ -1,12 +0,0 @@ -email === null || $request->email === '') { - throw new BadRequestException('email is required'); - } - if ($request->password === null || $request->password === '') { - throw new BadRequestException('password is required'); - } - - $user = $this->userRepo->findByEmail( - new EmailAddress($request->email), - ); - - if ($user === null) { - throw new UnauthorizedException('invalid credentials'); - } - - $passwordMatches = $this->hasher->verify( - $request->password, - $user->getPasswordHash(), - ); - - if (! $passwordMatches) { - throw new UnauthorizedException('invalid credentials'); - } - - return $user; - } -} diff --git a/backend/app/Auth/UseCases/AuthenticateUser/AuthenticateUserRequest.php b/backend/app/Auth/UseCases/AuthenticateUser/AuthenticateUserRequest.php deleted file mode 100644 index d7cf6dd..0000000 --- a/backend/app/Auth/UseCases/AuthenticateUser/AuthenticateUserRequest.php +++ /dev/null @@ -1,12 +0,0 @@ -clock->now(); - $expiresAt = $now->modify(self::SESSION_LIFETIME); - - return $this->sessionRepo->create(new CreateSessionDto( - token: $this->tokenGenerator->generate(), - user: $user, - createdAt: $now, - expiresAt: $expiresAt, - )); - } -} diff --git a/backend/app/Auth/UseCases/Logout/Logout.php b/backend/app/Auth/UseCases/Logout/Logout.php deleted file mode 100644 index f702346..0000000 --- a/backend/app/Auth/UseCases/Logout/Logout.php +++ /dev/null @@ -1,22 +0,0 @@ -sessionRepo->deleteByToken($token); - } -} diff --git a/backend/app/Controllers/AuthController.php b/backend/app/Controllers/AuthController.php deleted file mode 100644 index 5cb7c20..0000000 --- a/backend/app/Controllers/AuthController.php +++ /dev/null @@ -1,152 +0,0 @@ -parseBody($request); - - try { - $user = $this->authenticateUser->execute( - new AuthenticateUserRequest( - email: $body['email'] ?? null, - password: $body['password'] ?? null, - ), - ); - } catch (Throwable $exception) { - return $this->errorResponse($exception); - } - - $session = $this->createSession->execute($user); - - $response = $this->jsonResponse( - ['user' => $this->buildUserPayload($user)], - 200, - ); - - $cookieValue = sprintf( - '%s=%s; Expires=%s; Path=/; HttpOnly; SameSite=Lax', - AuthMiddleware::COOKIE_NAME, - $session->getToken(), - $session->getExpiresAt()->format('D, d-M-Y H:i:s T'), - ); - - return $response->withHeader('Set-Cookie', $cookieValue); - } - - public function logout( - ServerRequestInterface $request, - ): ResponseInterface { - $cookies = $request->getCookieParams(); - $token = $cookies[AuthMiddleware::COOKIE_NAME] ?? null; - - $this->logout->execute($token); - - $response = new Response(204); - - $cookieValue = sprintf( - '%s=; Expires=%s; Path=/; HttpOnly; SameSite=Lax', - AuthMiddleware::COOKIE_NAME, - 'Thu, 01-Jan-1970 00:00:00 GMT', - ); - - return $response->withHeader('Set-Cookie', $cookieValue); - } - - public function me( - ServerRequestInterface $request, - ): ResponseInterface { - $user = $request->getAttribute('user'); - - if (! $user instanceof User) { - return $this->jsonResponse( - ['error' => 'unauthenticated'], - 401, - ); - } - - return $this->jsonResponse( - ['user' => $this->buildUserPayload($user)], - 200, - ); - } - - private function buildUserPayload(User $user): array - { - return [ - 'id' => $user->getId(), - 'email' => $user->getEmail()->value(), - ]; - } - - private function jsonResponse( - array $data, - int $status, - ): ResponseInterface { - $response = new Response($status); - $response->getBody()->write(json_encode($data)); - - return $response->withHeader('Content-Type', 'application/json'); - } - - private function errorResponse(Throwable $exception): ResponseInterface - { - if ($exception instanceof BadRequestException) { - return $this->jsonResponse( - ['error' => $exception->getMessage()], - 400, - ); - } - if ($exception instanceof UnauthorizedException) { - return $this->jsonResponse( - ['error' => $exception->getMessage()], - 401, - ); - } - if ($exception instanceof DomainException) { - return $this->jsonResponse( - ['error' => $exception->getMessage()], - 409, - ); - } - throw $exception; - } - - private function parseBody(ServerRequestInterface $request): array - { - $contentType = $request->getHeaderLine('Content-Type'); - - if (str_contains($contentType, 'application/json')) { - $body = (string) $request->getBody(); - $decoded = json_decode($body, true); - - return is_array($decoded) ? $decoded : []; - } - - return (array) $request->getParsedBody(); - } -} diff --git a/backend/app/Exceptions/BadRequestException.php b/backend/app/Exceptions/BadRequestException.php deleted file mode 100644 index 1670d31..0000000 --- a/backend/app/Exceptions/BadRequestException.php +++ /dev/null @@ -1,9 +0,0 @@ -getCookieParams(); - $token = $cookies[self::COOKIE_NAME] ?? null; - - if (! is_string($token) || $token === '') { - return $this->unauthorized(); - } - - $session = $this->sessionRepo->findByToken($token); - - if ($session === null) { - return $this->unauthorized(); - } - - if ($session->isExpired($this->clock->now())) { - $this->sessionRepo->deleteByToken($token); - - return $this->unauthorized(); - } - - $request = $request->withAttribute('user', $session->getUser()); - - return $handler->handle($request); - } - - private function unauthorized(): ResponseInterface - { - $response = new Response(401); - $response->getBody()->write( - json_encode(['error' => 'unauthenticated']), - ); - - return $response->withHeader('Content-Type', 'application/json'); - } -} diff --git a/backend/app/Shared/ValueObject/EmailAddress.php b/backend/app/Shared/ValueObject/EmailAddress.php deleted file mode 100644 index ce7bcf9..0000000 --- a/backend/app/Shared/ValueObject/EmailAddress.php +++ /dev/null @@ -1,47 +0,0 @@ -normalized = $normalized; - } - - public function value(): string - { - return $this->normalized; - } - - public function equals(self $other): bool - { - return $this->normalized === $other->normalized; - } - - public function __toString(): string - { - return $this->normalized; - } -} diff --git a/backend/app/User/CreateUserDto.php b/backend/app/User/CreateUserDto.php deleted file mode 100644 index 848d9f7..0000000 --- a/backend/app/User/CreateUserDto.php +++ /dev/null @@ -1,14 +0,0 @@ -id; - } - - public function getEmail(): EmailAddress - { - return $this->email; - } - - public function getPasswordHash(): string - { - return $this->passwordHash; - } -} diff --git a/backend/app/User/UserRepository.php b/backend/app/User/UserRepository.php deleted file mode 100644 index 975b50d..0000000 --- a/backend/app/User/UserRepository.php +++ /dev/null @@ -1,14 +0,0 @@ -=3 <3.2.2" - }, - "require-dev": { - "doctrine/collections": "^1.6.8", - "doctrine/common": "^2.13.3 || ^3.2.2", - "phpspec/prophecy": "^1.10", - "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" - }, - "type": "library", - "autoload": { - "files": [ - "src/DeepCopy/deep_copy.php" - ], - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "support": { - "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" - }, - "funding": [ - { - "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", - "type": "tidelift" - } - ], - "time": "2025-08-01T08:46:24+00:00" - }, - { - "name": "nikic/php-parser", - "version": "v5.7.0", - "source": { - "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "php": ">=7.4" - }, - "require-dev": { - "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/php-parse" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "PhpParser\\": "lib/PhpParser" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Nikita Popov" - } - ], - "description": "A PHP parser written in PHP", - "keywords": [ - "parser", - "php" - ], - "support": { - "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" - }, - "time": "2025-12-06T11:56:16+00:00" - }, - { - "name": "phar-io/manifest", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/phar-io/manifest.git", - "reference": "54750ef60c58e43759730615a392c31c80e23176" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", - "reference": "54750ef60c58e43759730615a392c31c80e23176", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-phar": "*", - "ext-xmlwriter": "*", - "phar-io/version": "^3.0.1", - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", - "support": { - "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.4" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2024-03-03T12:33:53+00:00" - }, - { - "name": "phar-io/version", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/phar-io/version.git", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - }, - { - "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" - } - ], - "description": "Library for handling version information and constraints", - "support": { - "issues": "https://github.com/phar-io/version/issues", - "source": "https://github.com/phar-io/version/tree/3.2.1" - }, - "time": "2022-02-21T01:04:05+00:00" - }, - { - "name": "phpunit/php-code-coverage", - "version": "14.1.9", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "655533a65696bbc4231cd8027af150dadc40ec88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/655533a65696bbc4231cd8027af150dadc40ec88", - "reference": "655533a65696bbc4231cd8027af150dadc40ec88", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xmlwriter": "*", - "nikic/php-parser": "^5.7.0", - "php": ">=8.4", - "phpunit/php-text-template": "^6.0", - "sebastian/complexity": "^6.0", - "sebastian/environment": "^9.2", - "sebastian/git-state": "^1.0", - "sebastian/lines-of-code": "^5.0", - "sebastian/version": "^7.0", - "theseer/tokenizer": "^2.0.1" - }, - "require-dev": { - "phpunit/phpunit": "^13.1" - }, - "suggest": { - "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-main": "14.1.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.1.9" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", - "type": "tidelift" - } - ], - "time": "2026-05-16T05:16:14+00:00" - }, - { - "name": "phpunit/php-file-iterator", - "version": "7.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", - "reference": "6e5aa1fb0a95b1703d83e721299ee18bb4e2de50", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:33:26+00:00" - }, - { - "name": "phpunit/php-invoker", - "version": "7.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", - "reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "ext-pcntl": "*", - "phpunit/phpunit": "^13.0" - }, - "suggest": { - "ext-pcntl": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Invoke callables with a timeout", - "homepage": "https://github.com/sebastianbergmann/php-invoker/", - "keywords": [ - "process" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:34:47+00:00" - }, - { - "name": "phpunit/php-text-template", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4", - "reference": "a47af19f93f76aa3368303d752aa5272ca3299f4", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:36:37+00:00" - }, - { - "name": "phpunit/php-timer", - "version": "9.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6", - "reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "security": "https://github.com/sebastianbergmann/php-timer/security/policy", - "source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:37:53+00:00" - }, - { - "name": "phpunit/phpunit", - "version": "13.1.10", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "38959098d3c10660a189afaa35a94290c1de67bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38959098d3c10660a189afaa35a94290c1de67bb", - "reference": "38959098d3c10660a189afaa35a94290c1de67bb", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-libxml": "*", - "ext-mbstring": "*", - "ext-xml": "*", - "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.13.4", - "phar-io/manifest": "^2.0.4", - "phar-io/version": "^3.2.1", - "php": ">=8.4.1", - "phpunit/php-code-coverage": "^14.1.8", - "phpunit/php-file-iterator": "^7.0.0", - "phpunit/php-invoker": "^7.0.0", - "phpunit/php-text-template": "^6.0.0", - "phpunit/php-timer": "^9.0.0", - "sebastian/cli-parser": "^5.0.0", - "sebastian/comparator": "^8.1.3", - "sebastian/diff": "^8.3.0", - "sebastian/environment": "^9.3.0", - "sebastian/exporter": "^8.0.2", - "sebastian/git-state": "^1.0", - "sebastian/global-state": "^9.0.0", - "sebastian/object-enumerator": "^8.0.0", - "sebastian/recursion-context": "^8.0.0", - "sebastian/type": "^7.0.0", - "sebastian/version": "^7.0.0", - "staabm/side-effects-detector": "^1.0.5" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "13.1-dev" - } - }, - "autoload": { - "files": [ - "src/Framework/Assert/Functions.php" - ], - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/13.1.10" - }, - "funding": [ - { - "url": "https://phpunit.de/sponsoring.html", - "type": "other" - } - ], - "time": "2026-05-15T08:03:56+00:00" - }, - { - "name": "sebastian/cli-parser", - "version": "5.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/48a4654fa5e48c1c81214e9930048a572d4b23ca", - "reference": "48a4654fa5e48c1c81214e9930048a572d4b23ca", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for parsing CLI options", - "homepage": "https://github.com/sebastianbergmann/cli-parser", - "support": { - "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:39:44+00:00" - }, - { - "name": "sebastian/comparator", - "version": "8.1.4", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "1edd557042bf4ff9978ec125d8131b147d5c8224" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/1edd557042bf4ff9978ec125d8131b147d5c8224", - "reference": "1edd557042bf4ff9978ec125d8131b147d5c8224", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-mbstring": "*", - "php": ">=8.4", - "sebastian/diff": "^8.3", - "sebastian/exporter": "^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "suggest": { - "ext-bcmath": "For comparing BcMath\\Number objects" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.1-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/comparator/issues", - "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/8.1.4" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", - "type": "tidelift" - } - ], - "time": "2026-05-15T08:30:51+00:00" - }, - { - "name": "sebastian/complexity", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "c5651c795c98093480df79350cb050813fc7a2f3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3", - "reference": "c5651c795c98093480df79350cb050813fc7a2f3", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for calculating the complexity of PHP code units", - "homepage": "https://github.com/sebastianbergmann/complexity", - "support": { - "issues": "https://github.com/sebastianbergmann/complexity/issues", - "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/complexity", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:41:32+00:00" - }, - { - "name": "sebastian/diff", - "version": "8.3.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "b36d33b6e796513de7cb7df053afb3f55eefcd47" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b36d33b6e796513de7cb7df053afb3f55eefcd47", - "reference": "b36d33b6e796513de7cb7df053afb3f55eefcd47", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0", - "symfony/process": "^7.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.3-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/8.3.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/diff", - "type": "tidelift" - } - ], - "time": "2026-05-15T04:58:09+00:00" - }, - { - "name": "sebastian/environment", - "version": "9.3.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6767059a30e4277ac95ee034809e793528464768" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6767059a30e4277ac95ee034809e793528464768", - "reference": "6767059a30e4277ac95ee034809e793528464768", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "suggest": { - "ext-posix": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.3-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/environment/issues", - "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/9.3.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", - "type": "tidelift" - } - ], - "time": "2026-04-15T12:14:03+00:00" - }, - { - "name": "sebastian/exporter", - "version": "8.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "9cee180ebe62259e3ed48df2212d1fc8cfd971bb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/9cee180ebe62259e3ed48df2212d1fc8cfd971bb", - "reference": "9cee180ebe62259e3ed48df2212d1fc8cfd971bb", - "shasum": "" - }, - "require": { - "ext-mbstring": "*", - "php": ">=8.4", - "sebastian/recursion-context": "^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/exporter/issues", - "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/8.0.2" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", - "type": "tidelift" - } - ], - "time": "2026-04-15T12:38:05+00:00" - }, - { - "name": "sebastian/git-state", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/git-state.git", - "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/git-state/zipball/792a952e0eba55b6960a48aeceb9f371aad1f76b", - "reference": "792a952e0eba55b6960a48aeceb9f371aad1f76b", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for describing the state of a Git checkout", - "homepage": "https://github.com/sebastianbergmann/git-state", - "support": { - "issues": "https://github.com/sebastianbergmann/git-state/issues", - "security": "https://github.com/sebastianbergmann/git-state/security/policy", - "source": "https://github.com/sebastianbergmann/git-state/tree/1.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/git-state", - "type": "tidelift" - } - ], - "time": "2026-03-21T12:54:28+00:00" - }, - { - "name": "sebastian/global-state", - "version": "9.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e52e3dc22441e6218c710afe72c3042f8fc41ea7", - "reference": "e52e3dc22441e6218c710afe72c3042f8fc41ea7", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "sebastian/object-reflector": "^6.0", - "sebastian/recursion-context": "^8.0" - }, - "require-dev": { - "ext-dom": "*", - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "9.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "https://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/global-state/issues", - "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/9.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:45:13+00:00" - }, - { - "name": "sebastian/lines-of-code", - "version": "5.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", - "reference": "4f21bb7768e1c997722ccc7efb1d6b5c11bfd471", - "shasum": "" - }, - "require": { - "nikic/php-parser": "^5.0", - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "5.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library for counting the lines of code in PHP source code", - "homepage": "https://github.com/sebastianbergmann/lines-of-code", - "support": { - "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/5.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:45:54+00:00" - }, - { - "name": "sebastian/object-enumerator", - "version": "8.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", - "reference": "b39ab125fd9a7434b0ecbc4202eebce11a98cfc5", - "shasum": "" - }, - "require": { - "php": ">=8.4", - "sebastian/object-reflector": "^6.0", - "sebastian/recursion-context": "^8.0" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/8.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/object-enumerator", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:46:36+00:00" - }, - { - "name": "sebastian/object-reflector", - "version": "6.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", - "reference": "3ca042c2c60b0eab094f8a1b6a7093f4d4c72200", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "6.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Allows reflection of object attributes, including inherited and non-public ones", - "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "support": { - "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/6.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/object-reflector", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:47:13+00:00" - }, - { - "name": "sebastian/recursion-context", - "version": "8.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/74c5af21f6a5833e91767ca068c4d3dfec15317e", - "reference": "74c5af21f6a5833e91767ca068c4d3dfec15317e", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "8.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", - "support": { - "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/8.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:51:28+00:00" - }, - { - "name": "sebastian/type", - "version": "7.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "42412224607bd3931241bbd17f38e0f972f5a916" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/42412224607bd3931241bbd17f38e0f972f5a916", - "reference": "42412224607bd3931241bbd17f38e0f972f5a916", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "require-dev": { - "phpunit/phpunit": "^13.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", - "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/7.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/type", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:52:09+00:00" - }, - { - "name": "sebastian/version", - "version": "7.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/ad37a5552c8e2b88572249fdc19b6da7792e021b", - "reference": "ad37a5552c8e2b88572249fdc19b6da7792e021b", - "shasum": "" - }, - "require": { - "php": ">=8.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "7.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "security": "https://github.com/sebastianbergmann/version/security/policy", - "source": "https://github.com/sebastianbergmann/version/tree/7.0.0" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - }, - { - "url": "https://liberapay.com/sebastianbergmann", - "type": "liberapay" - }, - { - "url": "https://thanks.dev/u/gh/sebastianbergmann", - "type": "thanks_dev" - }, - { - "url": "https://tidelift.com/funding/github/packagist/sebastian/version", - "type": "tidelift" - } - ], - "time": "2026-02-06T04:52:52+00:00" - }, - { - "name": "staabm/side-effects-detector", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/staabm/side-effects-detector.git", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", - "reference": "d8334211a140ce329c13726d4a715adbddd0a163", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/extension-installer": "^1.4.3", - "phpstan/phpstan": "^1.12.6", - "phpunit/phpunit": "^9.6.21", - "symfony/var-dumper": "^5.4.43", - "tomasvotruba/type-coverage": "1.0.0", - "tomasvotruba/unused-public": "1.0.0" - }, - "type": "library", - "autoload": { - "classmap": [ - "lib/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A static analysis tool to detect side effects in PHP code", - "keywords": [ - "static analysis" - ], - "support": { - "issues": "https://github.com/staabm/side-effects-detector/issues", - "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" - }, - "funding": [ - { - "url": "https://github.com/staabm", - "type": "github" - } - ], - "time": "2024-10-20T05:08:20+00:00" - }, - { - "name": "theseer/tokenizer", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", - "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^8.1" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/2.0.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2025-12-08T11:19:18+00:00" - } - ], + "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": {}, diff --git a/backend/config/container.php b/backend/config/container.php deleted file mode 100644 index d01341b..0000000 --- a/backend/config/container.php +++ /dev/null @@ -1,37 +0,0 @@ -addDefinitions([ - - // Services - PasswordHasher::class => DI\create(BcryptPasswordHasher::class), - TokenGenerator::class => DI\create(RandomTokenGenerator::class), - Clock::class => DI\create(SystemClock::class), - - // Use cases - AuthenticateUser::class => DI\autowire(), - CreateSession::class => DI\autowire(), - Logout::class => DI\autowire(), - - // HTTP layer - AuthController::class => DI\autowire(), - AuthMiddleware::class => DI\autowire(), - -]); - -return $builder->build(); diff --git a/backend/config/routes.php b/backend/config/routes.php deleted file mode 100644 index cd9f359..0000000 --- a/backend/config/routes.php +++ /dev/null @@ -1,17 +0,0 @@ -get('/me', [AuthController::class, 'me']) - ->add(AuthMiddleware::class); - - $app->post('/login', [AuthController::class, 'login']); - - $app->post('/logout', [AuthController::class, 'logout']) - ->add(AuthMiddleware::class); -}; diff --git a/backend/phpunit.xml b/backend/phpunit.xml deleted file mode 100644 index e0a733a..0000000 --- a/backend/phpunit.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - tests/Unit - - - - - app - - - diff --git a/backend/public/index.php b/backend/public/index.php deleted file mode 100644 index 0209daf..0000000 --- a/backend/public/index.php +++ /dev/null @@ -1,19 +0,0 @@ -run(); -})(); diff --git a/backend/tests/Fakes/FakeClock.php b/backend/tests/Fakes/FakeClock.php deleted file mode 100644 index 7b14d8d..0000000 --- a/backend/tests/Fakes/FakeClock.php +++ /dev/null @@ -1,23 +0,0 @@ -currentTime; - } - - public function setTime(DateTimeImmutable $newTime): void - { - $this->currentTime = $newTime; - } -} diff --git a/backend/tests/Fakes/FakePasswordHasher.php b/backend/tests/Fakes/FakePasswordHasher.php deleted file mode 100644 index 507c966..0000000 --- a/backend/tests/Fakes/FakePasswordHasher.php +++ /dev/null @@ -1,18 +0,0 @@ -hash($password) === $hash; - } -} diff --git a/backend/tests/Fakes/FakeSessionRepository.php b/backend/tests/Fakes/FakeSessionRepository.php deleted file mode 100644 index 78a402d..0000000 --- a/backend/tests/Fakes/FakeSessionRepository.php +++ /dev/null @@ -1,46 +0,0 @@ -token, - user: $dto->user, - createdAt: $dto->createdAt, - expiresAt: $dto->expiresAt, - ); - $this->sessions[$dto->token] = $session; - - return $session; - } - - public function findByToken(string $token): ?Session - { - $session = $this->sessions[$token] ?? null; - - if ($session === null) { - return null; - } - - return new Session( - token: $session->getToken(), - user: $session->getUser(), - createdAt: $session->getCreatedAt(), - expiresAt: $session->getExpiresAt(), - ); - } - - public function deleteByToken(string $token): void - { - unset($this->sessions[$token]); - } -} diff --git a/backend/tests/Fakes/FakeTokenGenerator.php b/backend/tests/Fakes/FakeTokenGenerator.php deleted file mode 100644 index e808ffd..0000000 --- a/backend/tests/Fakes/FakeTokenGenerator.php +++ /dev/null @@ -1,28 +0,0 @@ -callCount >= count($this->tokens)) { - throw new RuntimeException( - 'FakeTokenGenerator exhausted' - ); - } - $token = $this->tokens[$this->callCount]; - $this->callCount++; - - return $token; - } -} diff --git a/backend/tests/Fakes/FakeUserRepository.php b/backend/tests/Fakes/FakeUserRepository.php deleted file mode 100644 index c816d1e..0000000 --- a/backend/tests/Fakes/FakeUserRepository.php +++ /dev/null @@ -1,63 +0,0 @@ -nextId(); - - $user = new User( - id: $id, - email: $dto->email, - passwordHash: $dto->passwordHash, - ); - - $this->users[$id] = $user; - - return $user; - } - - public function findByEmail(EmailAddress $email): ?User - { - foreach ($this->users as $user) { - if ($user->getEmail()->value() === $email->value()) { - return new User( - id: $user->getId(), - email: $user->getEmail(), - passwordHash: $user->getPasswordHash(), - ); - } - } - - return null; - } - - public function find(int $id): ?User - { - $user = $this->users[$id] ?? null; - - if ($user === null) { - return null; - } - - return new User( - id: $user->getId(), - email: $user->getEmail(), - passwordHash: $user->getPasswordHash(), - ); - } - - private function nextId(): int - { - return count($this->users); - } -} diff --git a/backend/tests/Unit/Auth/UseCases/AuthenticateUserTest.php b/backend/tests/Unit/Auth/UseCases/AuthenticateUserTest.php deleted file mode 100644 index 31ec0c9..0000000 --- a/backend/tests/Unit/Auth/UseCases/AuthenticateUserTest.php +++ /dev/null @@ -1,126 +0,0 @@ -userRepo = new FakeUserRepository(); - $this->hasher = new FakePasswordHasher(); - $this->useCase = new AuthenticateUser( - $this->userRepo, - $this->hasher, - ); - - $this->userRepo->create(new CreateUserDto( - email: new EmailAddress('user@example.com'), - passwordHash: $this->hasher->hash('correct-password'), - )); - } - - public function testAuthenticatesWithValidCredentials(): void - { - $user = $this->useCase->execute(new AuthenticateUserRequest( - email: 'user@example.com', - password: 'correct-password', - )); - - $this->assertSame('user@example.com', $user->getEmail()->value()); - } - - public function testThrowsBadRequestWhenEmailIsEmpty(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('email is required'); - - $this->useCase->execute(new AuthenticateUserRequest( - email: '', - password: 'some-password', - )); - } - - public function testThrowsBadRequestWhenPasswordIsEmpty(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('password is required'); - - $this->useCase->execute(new AuthenticateUserRequest( - email: 'user@example.com', - password: '', - )); - } - - public function testThrowsBadRequestWhenEmailIsNull(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('email is required'); - - $this->useCase->execute(new AuthenticateUserRequest( - email: null, - password: 'some-password', - )); - } - - public function testThrowsBadRequestWhenPasswordIsNull(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('password is required'); - - $this->useCase->execute(new AuthenticateUserRequest( - email: 'user@example.com', - password: null, - )); - } - - public function testThrowsUnauthorizedForUnknownEmail(): void - { - $this->expectException(UnauthorizedException::class); - $this->expectExceptionMessage('invalid credentials'); - - $this->useCase->execute(new AuthenticateUserRequest( - email: 'unknown@example.com', - password: 'some-password', - )); - } - - public function testThrowsUnauthorizedForWrongPassword(): void - { - $this->expectException(UnauthorizedException::class); - $this->expectExceptionMessage('invalid credentials'); - - $this->useCase->execute(new AuthenticateUserRequest( - email: 'user@example.com', - password: 'wrong-password', - )); - } - - public function testReturnsNewInstanceOnEachCall(): void - { - $user1 = $this->useCase->execute(new AuthenticateUserRequest( - email: 'user@example.com', - password: 'correct-password', - )); - - $user2 = $this->useCase->execute(new AuthenticateUserRequest( - email: 'user@example.com', - password: 'correct-password', - )); - - $this->assertNotSame($user1, $user2); - } -} diff --git a/backend/tests/Unit/Auth/UseCases/CreateSessionTest.php b/backend/tests/Unit/Auth/UseCases/CreateSessionTest.php deleted file mode 100644 index d08c05c..0000000 --- a/backend/tests/Unit/Auth/UseCases/CreateSessionTest.php +++ /dev/null @@ -1,102 +0,0 @@ -sessionRepo = new FakeSessionRepository(); - $this->tokenGenerator = new FakeTokenGenerator([ - 'token-1', - ]); - $this->clock = new FakeClock( - new DateTimeImmutable('2026-05-16 12:00:00'), - ); - $this->useCase = new CreateSession( - $this->sessionRepo, - $this->tokenGenerator, - $this->clock, - ); - - $userRepo = new FakeUserRepository(); - $this->user = $userRepo->create(new CreateUserDto( - email: new EmailAddress('user@example.com'), - passwordHash: 'hashed:password', - )); - } - - public function testCreatesSessionWithGivenToken(): void - { - $session = $this->useCase->execute($this->user); - - $this->assertSame('token-1', $session->getToken()); - } - - public function testCreatesSessionWithUser(): void - { - $session = $this->useCase->execute($this->user); - - $this->assertSame( - $this->user->getId(), - $session->getUser()->getId(), - ); - } - - public function testCreatesSessionWithCreatedAtFromClock(): void - { - $session = $this->useCase->execute($this->user); - - $this->assertEquals( - new DateTimeImmutable('2026-05-16 12:00:00'), - $session->getCreatedAt(), - ); - } - - public function testCreatesSessionWithExpirySevenDaysLater(): void - { - $session = $this->useCase->execute($this->user); - - $this->assertEquals( - new DateTimeImmutable('2026-05-23 12:00:00'), - $session->getExpiresAt(), - ); - } - - public function testPersistsSessionInRepository(): void - { - $session = $this->useCase->execute($this->user); - - $found = $this->sessionRepo->findByToken('token-1'); - - $this->assertNotNull($found); - $this->assertSame($session->getToken(), $found->getToken()); - } - - public function testFreshInstanceReturnedOnEachCall(): void - { - $session = $this->useCase->execute($this->user); - - $this->assertNotSame( - $session, - $this->sessionRepo->findByToken('token-1'), - ); - } -} diff --git a/backend/tests/Unit/Auth/UseCases/LogoutTest.php b/backend/tests/Unit/Auth/UseCases/LogoutTest.php deleted file mode 100644 index ffd4763..0000000 --- a/backend/tests/Unit/Auth/UseCases/LogoutTest.php +++ /dev/null @@ -1,67 +0,0 @@ -sessionRepo = new FakeSessionRepository(); - $this->useCase = new Logout($this->sessionRepo); - - $userRepo = new FakeUserRepository(); - $user = $userRepo->create(new CreateUserDto( - email: new EmailAddress('user@example.com'), - passwordHash: 'hashed:password', - )); - - $this->sessionRepo->create(new CreateSessionDto( - token: 'session-token', - user: $user, - createdAt: new DateTimeImmutable('2026-05-16 12:00:00'), - expiresAt: new DateTimeImmutable('2026-05-23 12:00:00'), - )); - } - - public function testDeletesSessionByToken(): void - { - $this->useCase->execute('session-token'); - - $this->assertNull( - $this->sessionRepo->findByToken('session-token'), - ); - } - - public function testDoesNotThrowForUnknownToken(): void - { - $this->useCase->execute('nonexistent-token'); - - $this->assertTrue(true); - } - - public function testDoesNotThrowForNullToken(): void - { - $this->useCase->execute(null); - - $this->assertTrue(true); - } - - public function testDoesNotThrowForEmptyStringToken(): void - { - $this->useCase->execute(''); - - $this->assertTrue(true); - } -} diff --git a/backend/tests/Unit/Controllers/AuthControllerTest.php b/backend/tests/Unit/Controllers/AuthControllerTest.php deleted file mode 100644 index c96e137..0000000 --- a/backend/tests/Unit/Controllers/AuthControllerTest.php +++ /dev/null @@ -1,170 +0,0 @@ -userRepo = new FakeUserRepository(); - $this->sessionRepo = new FakeSessionRepository(); - $this->hasher = new FakePasswordHasher(); - $this->tokenGenerator = new FakeTokenGenerator([ - 'session-token-1', - 'session-token-2', - ]); - $this->clock = new FakeClock( - new DateTimeImmutable('2026-05-16 12:00:00'), - ); - - $authenticateUser = new AuthenticateUser( - $this->userRepo, - $this->hasher, - ); - $createSession = new CreateSession( - $this->sessionRepo, - $this->tokenGenerator, - $this->clock, - ); - $logout = new Logout($this->sessionRepo); - - $this->controller = new AuthController( - $authenticateUser, - $createSession, - $logout, - ); - - $this->user = $this->userRepo->create(new CreateUserDto( - email: new EmailAddress('user@example.com'), - passwordHash: $this->hasher->hash('correct-password'), - )); - } - - public function testLoginReturnsUserAndSetsCookie(): void - { - $request = $this->jsonRequest('POST', '/login', [ - 'email' => 'user@example.com', - 'password' => 'correct-password', - ]); - - $response = $this->controller->login($request, new Response()); - - $this->assertSame(200, $response->getStatusCode()); - $this->assertStringContainsString( - 'user@example.com', - (string) $response->getBody(), - ); - - $cookieHeader = $response->getHeaderLine('Set-Cookie'); - $this->assertStringContainsString( - AuthMiddleware::COOKIE_NAME . '=session-token-1', - $cookieHeader, - ); - $this->assertStringContainsString('HttpOnly', $cookieHeader); - } - - public function testLoginReturns400ForMissingEmail(): void - { - $request = $this->jsonRequest('POST', '/login', [ - 'password' => 'correct-password', - ]); - - $response = $this->controller->login($request, new Response()); - - $this->assertSame(400, $response->getStatusCode()); - } - - public function testLoginReturns401ForInvalidCredentials(): void - { - $request = $this->jsonRequest('POST', '/login', [ - 'email' => 'user@example.com', - 'password' => 'wrong-password', - ]); - - $response = $this->controller->login($request, new Response()); - - $this->assertSame(401, $response->getStatusCode()); - } - - public function testLogoutClearsCookieAndReturns204(): void - { - $request = $this->createRequest() - ->withCookieParams([ - AuthMiddleware::COOKIE_NAME => 'session-token-1', - ]); - - $response = $this->controller->logout($request, new Response()); - - $this->assertSame(204, $response->getStatusCode()); - - $cookieHeader = $response->getHeaderLine('Set-Cookie'); - $this->assertStringContainsString( - AuthMiddleware::COOKIE_NAME . '=', - $cookieHeader, - ); - } - - public function testMeReturnsUserFromAttribute(): void - { - $request = $this->createRequest() - ->withAttribute('user', $this->user); - - $response = $this->controller->me($request, new Response()); - - $this->assertSame(200, $response->getStatusCode()); - $this->assertStringContainsString( - 'user@example.com', - (string) $response->getBody(), - ); - } - - private function jsonRequest( - string $method, - string $path, - array $body, - ): ServerRequestInterface { - $request = $this->createRequest($method, $path) - ->withHeader('Content-Type', 'application/json'); - $request->getBody()->write(json_encode($body)); - $request->getBody()->rewind(); - - return $request; - } - - private function createRequest( - string $method = 'POST', - string $path = '/', - ): ServerRequestInterface { - $factory = new ServerRequestFactory(); - - return $factory->createServerRequest($method, $path); - } -} diff --git a/backend/tests/Unit/Middleware/AuthMiddlewareTest.php b/backend/tests/Unit/Middleware/AuthMiddlewareTest.php deleted file mode 100644 index 9d2ae69..0000000 --- a/backend/tests/Unit/Middleware/AuthMiddlewareTest.php +++ /dev/null @@ -1,153 +0,0 @@ -sessionRepo = new FakeSessionRepository(); - $this->clock = new FakeClock( - new DateTimeImmutable('2026-05-16 12:00:00'), - ); - $this->middleware = new AuthMiddleware( - $this->sessionRepo, - $this->clock, - ); - - $userRepo = new FakeUserRepository(); - $hasher = new FakePasswordHasher(); - $this->user = $userRepo->create(new CreateUserDto( - email: new EmailAddress('user@example.com'), - passwordHash: $hasher->hash('password'), - )); - - $this->sessionRepo->create(new CreateSessionDto( - token: 'valid-token', - user: $this->user, - createdAt: new DateTimeImmutable('2026-05-16 12:00:00'), - expiresAt: new DateTimeImmutable('2026-05-23 12:00:00'), - )); - } - - public function testPassesRequestToHandlerWhenCookieIsValid(): void - { - $request = $this->createRequestWithCookie('valid-token'); - - $handler = $this->createMock(RequestHandlerInterface::class); - $handler->expects($this->once()) - ->method('handle') - ->with($this->callback(function (ServerRequestInterface $request) { - $user = $request->getAttribute('user'); - return $user instanceof User - && $user->getId() === $this->user->getId(); - })) - ->willReturn(new Response(200)); - - $response = $this->middleware->process($request, $handler); - - $this->assertSame(200, $response->getStatusCode()); - } - - public function testReturns401WhenCookieIsMissing(): void - { - $request = self::createRequest(); - $handler = $this->createMock(RequestHandlerInterface::class); - $handler->expects($this->never())->method('handle'); - - $response = $this->middleware->process($request, $handler); - - $this->assertSame(401, $response->getStatusCode()); - } - - public function testReturns401WhenCookieIsEmpty(): void - { - $request = $this->createRequestWithCookie(''); - $handler = $this->createMock(RequestHandlerInterface::class); - $handler->expects($this->never())->method('handle'); - - $response = $this->middleware->process($request, $handler); - - $this->assertSame(401, $response->getStatusCode()); - } - - public function testReturns401WhenTokenIsUnknown(): void - { - $request = $this->createRequestWithCookie('unknown-token'); - $handler = $this->createMock(RequestHandlerInterface::class); - $handler->expects($this->never())->method('handle'); - - $response = $this->middleware->process($request, $handler); - - $this->assertSame(401, $response->getStatusCode()); - } - - public function testReturns401WhenSessionIsExpired(): void - { - $this->clock->setTime( - new DateTimeImmutable('2026-05-30 12:00:00'), - ); - - $request = $this->createRequestWithCookie('valid-token'); - $handler = $this->createMock(RequestHandlerInterface::class); - $handler->expects($this->never())->method('handle'); - - $response = $this->middleware->process($request, $handler); - - $this->assertSame(401, $response->getStatusCode()); - } - - public function testDeletesExpiredSession(): void - { - $this->clock->setTime( - new DateTimeImmutable('2026-05-30 12:00:00'), - ); - - $request = $this->createRequestWithCookie('valid-token'); - $handler = $this->createStub(RequestHandlerInterface::class); - - $this->middleware->process($request, $handler); - - $this->assertNull( - $this->sessionRepo->findByToken('valid-token'), - ); - } - - private static function createRequest(): ServerRequestInterface - { - $factory = new ServerRequestFactory(); - - return $factory->createServerRequest('GET', '/'); - } - - private function createRequestWithCookie( - string $token, - ): ServerRequestInterface { - $request = self::createRequest(); - - return $request->withCookieParams([ - AuthMiddleware::COOKIE_NAME => $token, - ]); - } -}