'yes'], ); self::assertSame(418, $exception->getStatusCode()); self::assertSame("I'm a Teapot", $exception->getErrorTitle()); self::assertSame('Short and stout.', $exception->getMessage()); self::assertSame(['X-Teapot' => 'yes'], $exception->getHeaders()); // The code property of RuntimeException should be the HTTP status self::assertSame(418, $exception->getCode()); } #[Test] public function api_exception_with_previous(): void { $previous = new \RuntimeException('Root cause'); $exception = new ApiException(500, 'Internal Server Error', 'Something broke.', previous: $previous); self::assertSame($previous, $exception->getPrevious()); } #[Test] public function not_found_exception_defaults(): void { $exception = new NotFoundException(); self::assertSame(404, $exception->getStatusCode()); self::assertSame('Not Found', $exception->getErrorTitle()); self::assertSame('The requested resource was not found.', $exception->getMessage()); self::assertSame([], $exception->getHeaders()); } #[Test] public function not_found_exception_custom_detail(): void { $exception = new NotFoundException('Page /blog/missing was not found.'); self::assertSame(404, $exception->getStatusCode()); self::assertSame('Page /blog/missing was not found.', $exception->getMessage()); } #[Test] public function forbidden_exception_defaults(): void { $exception = new ForbiddenException(); self::assertSame(403, $exception->getStatusCode()); self::assertSame('Forbidden', $exception->getErrorTitle()); self::assertSame('You do not have permission to perform this action.', $exception->getMessage()); self::assertSame([], $exception->getHeaders()); } #[Test] public function unauthorized_exception_has_www_authenticate_header(): void { $exception = new UnauthorizedException(); self::assertSame(401, $exception->getStatusCode()); self::assertSame('Unauthorized', $exception->getErrorTitle()); self::assertSame('Authentication is required.', $exception->getMessage()); $headers = $exception->getHeaders(); self::assertArrayHasKey('WWW-Authenticate', $headers); self::assertSame('Bearer', $headers['WWW-Authenticate']); } #[Test] public function unauthorized_exception_custom_detail(): void { $exception = new UnauthorizedException('Token has expired.'); self::assertSame(401, $exception->getStatusCode()); self::assertSame('Token has expired.', $exception->getMessage()); self::assertSame('Bearer', $exception->getHeaders()['WWW-Authenticate']); } #[Test] public function validation_exception_includes_errors(): void { $errors = [ ['field' => 'email', 'message' => 'Email is required.'], ['field' => 'name', 'message' => 'Name must not be empty.'], ]; $exception = new ValidationException('Validation failed.', $errors); self::assertSame(422, $exception->getStatusCode()); self::assertSame('Unprocessable Entity', $exception->getErrorTitle()); self::assertSame('Validation failed.', $exception->getMessage()); self::assertSame($errors, $exception->getValidationErrors()); } #[Test] public function validation_exception_defaults(): void { $exception = new ValidationException(); self::assertSame(422, $exception->getStatusCode()); self::assertSame('The request data is invalid.', $exception->getMessage()); self::assertSame([], $exception->getValidationErrors()); } #[Test] public function conflict_exception_defaults(): void { $exception = new ConflictException(); self::assertSame(409, $exception->getStatusCode()); self::assertSame('Conflict', $exception->getErrorTitle()); self::assertSame('The resource has been modified. Refresh and try again.', $exception->getMessage()); self::assertSame([], $exception->getHeaders()); } #[Test] public function conflict_exception_custom_detail(): void { $exception = new ConflictException('ETag mismatch for page /about.'); self::assertSame(409, $exception->getStatusCode()); self::assertSame('ETag mismatch for page /about.', $exception->getMessage()); } #[Test] public function all_exceptions_extend_api_exception(): void { self::assertInstanceOf(ApiException::class, new NotFoundException()); self::assertInstanceOf(ApiException::class, new ForbiddenException()); self::assertInstanceOf(ApiException::class, new UnauthorizedException()); self::assertInstanceOf(ApiException::class, new ValidationException()); self::assertInstanceOf(ApiException::class, new ConflictException()); } #[Test] public function api_exception_extends_runtime_exception(): void { self::assertInstanceOf(\RuntimeException::class, new ApiException(500, 'Error', 'test')); } }