From 7d0dd2539f114ae544173fc304d1c778058cd192 Mon Sep 17 00:00:00 2001 From: Wilmer Arambula Date: Tue, 19 Aug 2025 14:36:57 -0400 Subject: [PATCH 1/8] test: Add test for rendering exceptions in `StatelessApplication` with proper response validation. --- tests/http/StatelessApplicationTest.php | 53 +++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/http/StatelessApplicationTest.php b/tests/http/StatelessApplicationTest.php index 695308b7..1c38a6af 100644 --- a/tests/http/StatelessApplicationTest.php +++ b/tests/http/StatelessApplicationTest.php @@ -897,6 +897,59 @@ public function testRecalculateMemoryLimitAfterResetAndIniChange(): void ini_set('memory_limit', $originalLimit); } + /** + * @throws InvalidConfigException if the configuration is invalid or incomplete. + */ + public function testRenderExceptionPassesExceptionParameterToTemplateView(): void + { + $_SERVER = [ + 'REQUEST_METHOD' => 'GET', + 'REQUEST_URI' => 'site/trigger-exception', + ]; + + $request = FactoryHelper::createServerRequestCreator()->createFromGlobals(); + + $app = $this->statelessApplication( + [ + 'components' => [ + 'errorHandler' => [ + 'errorAction' => null, + ], + ], + ], + ); + + $response = $app->handle($request); + + self::assertSame( + 500, + $response->getStatusCode(), + "Response 'status code' should be '500' when exception occurs and template rendering is used in " . + "'StatelessApplication'.", + ); + self::assertSame( + 'text/html; charset=UTF-8', + $response->getHeaderLine('Content-Type'), + "Response 'Content-Type' should be 'text/html; charset=UTF-8' for exception template rendering in " . + "'StatelessApplication'.", + ); + + $responseBody = $response->getBody()->getContents(); + + self::assertStringContainsString( + '
Exception (Exception) 'yii\base\Exception' with message 'Exception error message.'',
+            $responseBody,
+            "Response 'body' should contain exception class and message when 'exception' parameter is passed to " .
+            "'renderFile()' method in 'StatelessApplication'.",
+        );
+        self::assertStringContainsString(
+            ''site/trigger-ex...'',
+            $responseBody,
+            "Response 'body' should contain stack trace information when 'exception' parameter is properly passed " .
+            "to 'renderFile()' method in 'StatelessApplication'.",
+        );
+    }
+
     /**
      * @throws InvalidConfigException if the configuration is invalid or incomplete.
      */

From b9cc20516a50258745ebfa7afd7746f17aca7c7d Mon Sep 17 00:00:00 2001
From: Wilmer Arambula 
Date: Tue, 19 Aug 2025 14:41:36 -0400
Subject: [PATCH 2/8] fix: Update stack trace information in response body for
 exception handling in `StatelessApplication` class.

---
 tests/http/StatelessApplicationTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/http/StatelessApplicationTest.php b/tests/http/StatelessApplicationTest.php
index 1c38a6af..710dcec6 100644
--- a/tests/http/StatelessApplicationTest.php
+++ b/tests/http/StatelessApplicationTest.php
@@ -943,7 +943,7 @@ public function testRenderExceptionPassesExceptionParameterToTemplateView(): voi
             "'renderFile()' method in 'StatelessApplication'.",
         );
         self::assertStringContainsString(
-            ''site/trigger-ex...'',
+            '[internal function]: yii2\extensions\psrbridge\tests\support\stub\SiteController->actionTriggerException()',
             $responseBody,
             "Response 'body' should contain stack trace information when 'exception' parameter is properly passed " .
             "to 'renderFile()' method in 'StatelessApplication'.",

From 0c568e2d40ef49924a251baab703e9f0be44824c Mon Sep 17 00:00:00 2001
From: Wilmer Arambula 
Date: Tue, 19 Aug 2025 15:40:11 -0400
Subject: [PATCH 3/8] test: Enhance exception rendering test to include
 environment handling and buffer management.

---
 tests/http/StatelessApplicationTest.php | 21 +++++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/tests/http/StatelessApplicationTest.php b/tests/http/StatelessApplicationTest.php
index 710dcec6..dc05042a 100644
--- a/tests/http/StatelessApplicationTest.php
+++ b/tests/http/StatelessApplicationTest.php
@@ -900,8 +900,13 @@ public function testRecalculateMemoryLimitAfterResetAndIniChange(): void
     /**
      * @throws InvalidConfigException if the configuration is invalid or incomplete.
      */
+    #[RequiresPhpExtension('runkit7')]
     public function testRenderExceptionPassesExceptionParameterToTemplateView(): void
     {
+        @\runkit_constant_redefine('YII_ENV_TEST', false);
+
+        $initialBufferLevel = ob_get_level();
+
         $_SERVER = [
             'REQUEST_METHOD' => 'GET',
             'REQUEST_URI' => 'site/trigger-exception',
@@ -937,17 +942,21 @@ public function testRenderExceptionPassesExceptionParameterToTemplateView(): voi
         $responseBody = $response->getBody()->getContents();
 
         self::assertStringContainsString(
-            '
Exception (Exception) 'yii\base\Exception' with message 'Exception error message.'',
+            'yii\base\Exception',
             $responseBody,
-            "Response 'body' should contain exception class and message when 'exception' parameter is passed to " .
-            "'renderFile()' method in 'StatelessApplication'.",
+            "Response 'body' should contain exception class when 'exception' parameter is passed to 'renderFile()'.",
         );
         self::assertStringContainsString(
-            '[internal function]: yii2\extensions\psrbridge\tests\support\stub\SiteController->actionTriggerException()',
+            'Exception error message.',
             $responseBody,
-            "Response 'body' should contain stack trace information when 'exception' parameter is properly passed " .
-            "to 'renderFile()' method in 'StatelessApplication'.",
+            "Response 'body' should contain exception message when 'exception' parameter is passed to 'renderFile()'.",
         );
+
+        while (ob_get_level() < $initialBufferLevel) {
+            ob_start();
+        }
+
+        @\runkit_constant_redefine('YII_ENV_TEST', true);
     }
 
     /**

From 922ed3512c1a66a2e22ebc2d3f63efa2186825db Mon Sep 17 00:00:00 2001
From: Wilmer Arambula 
Date: Tue, 19 Aug 2025 15:53:34 -0400
Subject: [PATCH 4/8] test: Enhance exception handling tests to verify
 inclusion of file path in response body.

---
 src/http/ErrorHandler.php               | 1 +
 tests/http/StatelessApplicationTest.php | 6 ++++++
 2 files changed, 7 insertions(+)

diff --git a/src/http/ErrorHandler.php b/src/http/ErrorHandler.php
index 26f25e30..6fc407b5 100644
--- a/src/http/ErrorHandler.php
+++ b/src/http/ErrorHandler.php
@@ -234,6 +234,7 @@ protected function renderException($exception): Response
                 }
 
                 $file = $useErrorView ? $this->errorView : $this->exceptionView;
+
                 $response->data = $this->renderFile($file, ['exception' => $exception]);
             }
         } elseif ($response->format === Response::FORMAT_RAW) {
diff --git a/tests/http/StatelessApplicationTest.php b/tests/http/StatelessApplicationTest.php
index dc05042a..1e30898f 100644
--- a/tests/http/StatelessApplicationTest.php
+++ b/tests/http/StatelessApplicationTest.php
@@ -951,6 +951,12 @@ public function testRenderExceptionPassesExceptionParameterToTemplateView(): voi
             $responseBody,
             "Response 'body' should contain exception message when 'exception' parameter is passed to 'renderFile()'.",
         );
+        self::assertStringContainsString(
+            'yii2-extensions\psr-bridge\tests\support\stub\SiteController.php',
+            $responseBody,
+            "Response 'body' should contain file path where exception occurred when 'exception' parameter is passed " .
+            "to 'renderFile()'.",
+        );
 
         while (ob_get_level() < $initialBufferLevel) {
             ob_start();

From a76bf49e0b2daa36251f7e1506103fa9c988823e Mon Sep 17 00:00:00 2001
From: Wilmer Arambula 
Date: Tue, 19 Aug 2025 15:57:50 -0400
Subject: [PATCH 5/8] test: Update exception handling test to verify response
 body contains exception trace instead of file path.

---
 tests/http/StatelessApplicationTest.php | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/tests/http/StatelessApplicationTest.php b/tests/http/StatelessApplicationTest.php
index 1e30898f..e7aa46ee 100644
--- a/tests/http/StatelessApplicationTest.php
+++ b/tests/http/StatelessApplicationTest.php
@@ -952,10 +952,9 @@ public function testRenderExceptionPassesExceptionParameterToTemplateView(): voi
             "Response 'body' should contain exception message when 'exception' parameter is passed to 'renderFile()'.",
         );
         self::assertStringContainsString(
-            'yii2-extensions\psr-bridge\tests\support\stub\SiteController.php',
+            '[internal function]: yii2\extensions\psrbridge\tests\support\stub\SiteController->actionTriggerException()',
             $responseBody,
-            "Response 'body' should contain file path where exception occurred when 'exception' parameter is passed " .
-            "to 'renderFile()'.",
+            "Response 'body' should contain exception trace when 'exception' parameter is passed to 'renderFile()'.",
         );
 
         while (ob_get_level() < $initialBufferLevel) {

From f548ef18e3012591febe17375df14f90f54f4a39 Mon Sep 17 00:00:00 2001
From: Wilmer Arambula 
Date: Tue, 19 Aug 2025 16:25:28 -0400
Subject: [PATCH 6/8] test: Improve error handling in
 `StatelessApplicationTest` class to ensure proper exception management and
 response validation.

---
 tests/http/StatelessApplicationTest.php | 116 +++++++++++++++---------
 1 file changed, 71 insertions(+), 45 deletions(-)

diff --git a/tests/http/StatelessApplicationTest.php b/tests/http/StatelessApplicationTest.php
index e7aa46ee..6d79a141 100644
--- a/tests/http/StatelessApplicationTest.php
+++ b/tests/http/StatelessApplicationTest.php
@@ -4,6 +4,7 @@
 
 namespace yii2\extensions\psrbridge\tests\http;
 
+use ErrorException;
 use HttpSoft\Message\{ServerRequestFactory, StreamFactory, UploadedFileFactory};
 use PHPUnit\Framework\Attributes\{DataProviderExternal, Group, RequiresPhpExtension};
 use Psr\Http\Message\{ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface};
@@ -907,61 +908,86 @@ public function testRenderExceptionPassesExceptionParameterToTemplateView(): voi
 
         $initialBufferLevel = ob_get_level();
 
-        $_SERVER = [
-            'REQUEST_METHOD' => 'GET',
-            'REQUEST_URI' => 'site/trigger-exception',
-        ];
+        $originalErrorHandler = set_error_handler(
+            static function ($severity, $message, $file, $line) {
+            if (str_contains($message, 'Undefined variable $exception')) {
+                throw new Exception('Undefined variable $exception');
+            }
 
-        $request = FactoryHelper::createServerRequestCreator()->createFromGlobals();
+            return false;
+            },
+        );
 
-        $app = $this->statelessApplication(
-            [
-                'components' => [
-                    'errorHandler' => [
-                        'errorAction' => null,
+        try {
+            $_SERVER = [
+                'REQUEST_METHOD' => 'GET',
+                'REQUEST_URI' => 'site/trigger-exception',
+            ];
+
+            $request = FactoryHelper::createServerRequestCreator()->createFromGlobals();
+
+            $app = $this->statelessApplication(
+                [
+                    'components' => [
+                        'errorHandler' => [
+                            'errorAction' => null,
+                        ],
                     ],
                 ],
-            ],
-        );
+            );
 
-        $response = $app->handle($request);
+            $response = $app->handle($request);
 
-        self::assertSame(
-            500,
-            $response->getStatusCode(),
-            "Response 'status code' should be '500' when exception occurs and template rendering is used in " .
-            "'StatelessApplication'.",
-        );
-        self::assertSame(
-            'text/html; charset=UTF-8',
-            $response->getHeaderLine('Content-Type'),
-            "Response 'Content-Type' should be 'text/html; charset=UTF-8' for exception template rendering in " .
-            "'StatelessApplication'.",
-        );
+            self::assertSame(
+                500,
+                $response->getStatusCode(),
+                "Response 'status code' should be '500' when exception occurs and template rendering is used in " .
+                "'StatelessApplication'.",
+            );
+            self::assertSame(
+                'text/html; charset=UTF-8',
+                $response->getHeaderLine('Content-Type'),
+                "Response 'Content-Type' should be 'text/html; charset=UTF-8' for exception template rendering in " .
+                "'StatelessApplication'.",
+            );
 
-        $responseBody = $response->getBody()->getContents();
+            $responseBody = $response->getBody()->getContents();
 
-        self::assertStringContainsString(
-            'yii\base\Exception',
-            $responseBody,
-            "Response 'body' should contain exception class when 'exception' parameter is passed to 'renderFile()'.",
-        );
-        self::assertStringContainsString(
-            'Exception error message.',
-            $responseBody,
-            "Response 'body' should contain exception message when 'exception' parameter is passed to 'renderFile()'.",
-        );
-        self::assertStringContainsString(
-            '[internal function]: yii2\extensions\psrbridge\tests\support\stub\SiteController->actionTriggerException()',
-            $responseBody,
-            "Response 'body' should contain exception trace when 'exception' parameter is passed to 'renderFile()'.",
-        );
+            self::assertStringContainsString(
+                'yii\base\Exception',
+                $responseBody,
+                "Response 'body' should contain exception class when 'exception' parameter is passed to 'renderFile()'.",
+            );
+            self::assertStringContainsString(
+                'Exception error message.',
+                $responseBody,
+                "Response 'body' should contain exception message when 'exception' parameter is passed to 'renderFile()'.",
+            );
+            self::assertStringContainsString(
+                '[internal function]: yii2\extensions\psrbridge\tests\support\stub\SiteController->actionTriggerException()',
+                $responseBody,
+                "Response 'body' should contain exception trace when 'exception' parameter is passed to 'renderFile()'.",
+            );
+        } finally {
+            if ($originalErrorHandler !== null) {
+                set_error_handler($originalErrorHandler);
 
-        while (ob_get_level() < $initialBufferLevel) {
-            ob_start();
-        }
+                self::assertStringNotContainsString(
+                    'Undefined variable $exception',
+                    $responseBody,
+                    "Response 'body' should not contain 'Undefined variable \$exception' after restoring original error " .
+                    "handler in 'StatelessApplication'.",
+                );
+            } else {
+                restore_error_handler();
+            }
 
-        @\runkit_constant_redefine('YII_ENV_TEST', true);
+            while (ob_get_level() < $initialBufferLevel) {
+                ob_start();
+            }
+
+            @\runkit_constant_redefine('YII_ENV_TEST', true);
+        }
     }
 
     /**

From 85db0f4f76a04bbe0536b7fe66d29f3065b34f98 Mon Sep 17 00:00:00 2001
From: Wilmer Arambula 
Date: Tue, 19 Aug 2025 20:25:53 +0000
Subject: [PATCH 7/8] Apply fixes from StyleCI

---
 tests/http/StatelessApplicationTest.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/http/StatelessApplicationTest.php b/tests/http/StatelessApplicationTest.php
index 6d79a141..436b57c0 100644
--- a/tests/http/StatelessApplicationTest.php
+++ b/tests/http/StatelessApplicationTest.php
@@ -4,7 +4,6 @@
 
 namespace yii2\extensions\psrbridge\tests\http;
 
-use ErrorException;
 use HttpSoft\Message\{ServerRequestFactory, StreamFactory, UploadedFileFactory};
 use PHPUnit\Framework\Attributes\{DataProviderExternal, Group, RequiresPhpExtension};
 use Psr\Http\Message\{ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface};

From 3e3d4d011c99b666786e10a59191586039eb5ec6 Mon Sep 17 00:00:00 2001
From: Wilmer Arambula 
Date: Tue, 19 Aug 2025 17:11:41 -0400
Subject: [PATCH 8/8] test: Refactor error handling in
 `StatelessApplicationTest` class to capture warnings and validate exception
 rendering.

---
 tests/http/StatelessApplicationTest.php | 84 ++++++++++++++-----------
 1 file changed, 48 insertions(+), 36 deletions(-)

diff --git a/tests/http/StatelessApplicationTest.php b/tests/http/StatelessApplicationTest.php
index 6d79a141..c4b316a9 100644
--- a/tests/http/StatelessApplicationTest.php
+++ b/tests/http/StatelessApplicationTest.php
@@ -4,7 +4,6 @@
 
 namespace yii2\extensions\psrbridge\tests\http;
 
-use ErrorException;
 use HttpSoft\Message\{ServerRequestFactory, StreamFactory, UploadedFileFactory};
 use PHPUnit\Framework\Attributes\{DataProviderExternal, Group, RequiresPhpExtension};
 use Psr\Http\Message\{ServerRequestFactoryInterface, StreamFactoryInterface, UploadedFileFactoryInterface};
@@ -39,8 +38,11 @@
 use function ob_get_level;
 use function ob_start;
 use function preg_quote;
+use function restore_error_handler;
 use function session_name;
+use function set_error_handler;
 use function sprintf;
+use function str_contains;
 use function str_starts_with;
 use function uniqid;
 
@@ -908,24 +910,31 @@ public function testRenderExceptionPassesExceptionParameterToTemplateView(): voi
 
         $initialBufferLevel = ob_get_level();
 
-        $originalErrorHandler = set_error_handler(
-            static function ($severity, $message, $file, $line) {
-            if (str_contains($message, 'Undefined variable $exception')) {
-                throw new Exception('Undefined variable $exception');
-            }
+        $_SERVER = [
+            'REQUEST_METHOD' => 'GET',
+            'REQUEST_URI' => 'site/trigger-exception',
+        ];
+
+        $request = FactoryHelper::createServerRequestCreator()->createFromGlobals();
+
+        $warningsCaptured = [];
+
+        set_error_handler(
+            static function ($errno, $errstr, $errfile, $errline) use (&$warningsCaptured): bool {
+                if ($errno === E_WARNING || $errno === E_NOTICE) {
+                    $warningsCaptured[] = [
+                        'type' => $errno,
+                        'message' => $errstr,
+                        'file' => $errfile,
+                        'line' => $errline,
+                    ];
+                }
 
-            return false;
+                return false;
             },
         );
 
         try {
-            $_SERVER = [
-                'REQUEST_METHOD' => 'GET',
-                'REQUEST_URI' => 'site/trigger-exception',
-            ];
-
-            $request = FactoryHelper::createServerRequestCreator()->createFromGlobals();
-
             $app = $this->statelessApplication(
                 [
                     'components' => [
@@ -938,49 +947,49 @@ static function ($severity, $message, $file, $line) {
 
             $response = $app->handle($request);
 
+            $undefinedExceptionWarnings = array_filter(
+                $warningsCaptured,
+                static fn(array $warning): bool => str_contains($warning['message'], 'Undefined variable'),
+            );
+
+            self::assertEmpty(
+                $undefinedExceptionWarnings,
+                "Should be no 'Undefined variable' warnings, confirming that 'exception' parameter is defined in the " .
+                "view context when rendering exception in 'StatelessApplication'.",
+            );
             self::assertSame(
                 500,
                 $response->getStatusCode(),
                 "Response 'status code' should be '500' when exception occurs and template rendering is used in " .
                 "'StatelessApplication'.",
             );
-            self::assertSame(
-                'text/html; charset=UTF-8',
-                $response->getHeaderLine('Content-Type'),
-                "Response 'Content-Type' should be 'text/html; charset=UTF-8' for exception template rendering in " .
-                "'StatelessApplication'.",
-            );
 
             $responseBody = $response->getBody()->getContents();
 
             self::assertStringContainsString(
-                'yii\base\Exception',
+                Exception::class,
                 $responseBody,
                 "Response 'body' should contain exception class when 'exception' parameter is passed to 'renderFile()'.",
             );
+            self::assertStringContainsString(
+                'Stack trace:',
+                $responseBody,
+                "Response 'body' should contain 'Stack trace:' section, confirming exception object is available to template.",
+            );
             self::assertStringContainsString(
                 'Exception error message.',
                 $responseBody,
-                "Response 'body' should contain exception message when 'exception' parameter is passed to 'renderFile()'.",
+                "Response 'body' should contain the exact exception message 'Exception error message.', " .
+                'confirming the exception object was properly passed to the view.',
             );
             self::assertStringContainsString(
-                '[internal function]: yii2\extensions\psrbridge\tests\support\stub\SiteController->actionTriggerException()',
+                'SiteController.php',
                 $responseBody,
-                "Response 'body' should contain exception trace when 'exception' parameter is passed to 'renderFile()'.",
+                "Response 'body' should contain reference to 'SiteController.php' where the exception was thrown, " .
+                'confirming full exception details are available in the view.',
             );
         } finally {
-            if ($originalErrorHandler !== null) {
-                set_error_handler($originalErrorHandler);
-
-                self::assertStringNotContainsString(
-                    'Undefined variable $exception',
-                    $responseBody,
-                    "Response 'body' should not contain 'Undefined variable \$exception' after restoring original error " .
-                    "handler in 'StatelessApplication'.",
-                );
-            } else {
-                restore_error_handler();
-            }
+            restore_error_handler();
 
             while (ob_get_level() < $initialBufferLevel) {
                 ob_start();
@@ -1720,6 +1729,9 @@ public function testReturnJsonResponseWithQueryParametersForSiteGetRoute(): void
         );
     }
 
+    /**
+     * @throws InvalidConfigException if the configuration is invalid or incomplete.
+     */
     public function testReturnJsonResponseWithQueryParamsForSiteQueryRoute(): void
     {
         $_GET = ['q' => '1'];