diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7439044..3be5a4c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,10 +19,12 @@ jobs: with: key: phpstan path: .phpstan-cache - - name: Run PHPStan - run: vendor/bin/phpstan - name: Run ECS run: php vendor/bin/ecs + - name: Build test files + run: vendor/bin/codecept build + - name: Run PHPStan + run: vendor/bin/phpstan tests: runs-on: ubuntu-latest strategy: @@ -52,6 +54,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + fetch-depth: 0 persist-credentials: false - uses: actions/setup-node@v4 with: @@ -62,7 +65,7 @@ jobs: -p "@semantic-release/release-notes-generator" -p conventional-changelog-conventionalcommits -p semantic-release - -- semantic-release --dry-run + -- semantic-release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} permissions: diff --git a/ecs.php b/ecs.php index 0a1526b..dcbd9ef 100644 --- a/ecs.php +++ b/ecs.php @@ -58,7 +58,8 @@ ForbiddenFunctionsSniff::class => [ 'tests/**', 'console/**' - ] + ], + 'tests/_support/_generated' ]); // $ecsConfig->skip([ diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ce4877d..01178ed 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1092,12 +1092,6 @@ parameters: count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:createAndSetCsrfCookie\(\) should return array\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:dontSee\(\) has parameter \$selector with no value type specified in iterable type array\.$#' identifier: missingType.iterableValue @@ -1194,12 +1188,6 @@ parameters: count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:getInternalDomains\(\) should return non\-empty\-list\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabAttributeFrom\(\) has parameter \$cssOrXpath with no type specified\.$#' identifier: missingType.parameter @@ -1212,48 +1200,18 @@ parameters: count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabFixture\(\) should return yii\\db\\ActiveRecord\|yii\\test\\Fixture\|null but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabFixtures\(\) has invalid return type tests\\_generated\\Fixture\.$#' identifier: class.notFound count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabFixtures\(\) should return array\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - - - message: '#^Method tests\\FunctionalTester\:\:grabLastSentEmail\(\) should return yii\\mail\\BaseMessage\|null but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabMultiple\(\) has parameter \$cssOrXpath with no type specified\.$#' identifier: missingType.parameter count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabMultiple\(\) should return array\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - - - message: '#^Method tests\\FunctionalTester\:\:grabPageSource\(\) should return string but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabRecord\(\) has invalid return type tests\\_generated\\ActiveRecordInterface\.$#' identifier: class.notFound @@ -1266,30 +1224,12 @@ parameters: count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabRecord\(\) should return array\|yii\\db\\ActiveRecordInterface\|null but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - - - message: '#^Method tests\\FunctionalTester\:\:grabSentEmails\(\) has invalid return type tests\\_generated\\BaseMessage\.$#' - identifier: class.notFound - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabSentEmails\(\) has invalid return type tests\\_generated\\MessageInterface\.$#' identifier: class.notFound count: 1 path: tests/_support/FunctionalTester.php - - - message: '#^Method tests\\FunctionalTester\:\:grabSentEmails\(\) should return list\ but returns mixed\.$#' - identifier: return.type - count: 1 - path: tests/_support/FunctionalTester.php - - message: '#^Method tests\\FunctionalTester\:\:grabTextFrom\(\) has parameter \$cssOrXPathOrRegex with no type specified\.$#' identifier: missingType.parameter @@ -1758,12 +1698,6 @@ parameters: count: 1 path: tests/cases/pageCacheHeaderAlreadySent/controllers/UserController.php - - - message: '#^Method app\\pageCacheHeaderAlreadySent\\controllers\\UserController\:\:behaviors\(\) return type has no value type specified in iterable type array\.$#' - identifier: missingType.iterableValue - count: 1 - path: tests/cases/pageCacheHeaderAlreadySent/controllers/UserController.php - - message: '#^Method PageCest\:\:testCache\(\) has no return type specified\.$#' identifier: missingType.return diff --git a/phpstan.neon b/phpstan.neon index fb2d525..e695b2d 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -17,6 +17,8 @@ parameters: checkMaybeUndefinedVariables: true treatPhpDocTypesAsCertain: false ignoreErrors: + - identifier: missingType.iterableValue + path: tests/_support/_generated/FunctionalTesterActions.php - identifier: return.type path: tests/_support/_generated/FunctionalTesterActions.php message: "# but returns mixed.$#" diff --git a/src/Codeception/Lib/Connector/Yii2.php b/src/Codeception/Lib/Connector/Yii2.php index bd4b180..379bfc1 100644 --- a/src/Codeception/Lib/Connector/Yii2.php +++ b/src/Codeception/Lib/Connector/Yii2.php @@ -10,6 +10,7 @@ use Codeception\Lib\Connector\Yii2\TestMailer; use Codeception\Util\Debug; use InvalidArgumentException; +use RuntimeException; use Symfony\Component\BrowserKit\AbstractBrowser as Client; use Symfony\Component\BrowserKit\Cookie; use Symfony\Component\BrowserKit\CookieJar; @@ -21,7 +22,9 @@ use yii\base\ExitException; use yii\base\Security; use yii\base\UserException; -use yii\mail\BaseMessage; +use yii\mail\BaseMailer; +use yii\mail\MailEvent; +use yii\mail\MessageInterface; use yii\web\Application; use yii\web\IdentityInterface; use yii\web\Request as YiiRequest; @@ -36,7 +39,22 @@ final class Yii2 extends Client { use Shared\PhpSuperGlobalsConverter; - public const CLEAN_METHODS = [ + public const array MAIL_METHODS = [ + self::MAIL_CATCH, + self::MAIL_EVENT_AFTER, + self::MAIL_EVENT_BEFORE, + self::MAIL_IGNORE + ]; + + public const string MAIL_CATCH = 'catch'; + + public const string MAIL_EVENT_AFTER = 'after'; + + public const string MAIL_EVENT_BEFORE = 'before'; + + public const string MAIL_IGNORE = 'ignore'; + + public const array CLEAN_METHODS = [ self::CLEAN_RECREATE, self::CLEAN_CLEAR, self::CLEAN_FORCE_RECREATE, @@ -47,12 +65,12 @@ final class Yii2 extends Client * Clean the response object by recreating it. * This might lose behaviors / event handlers / other changes that are done in the application bootstrap phase. */ - public const CLEAN_RECREATE = 'recreate'; + public const string CLEAN_RECREATE = 'recreate'; /** * Same as recreate but will not warn when behaviors / event handlers are lost. */ - public const CLEAN_FORCE_RECREATE = 'force_recreate'; + public const string CLEAN_FORCE_RECREATE = 'force_recreate'; /** * Clean the response object by resetting specific properties via its' `clear()` method. @@ -60,33 +78,38 @@ final class Yii2 extends Client * * @see \yii\web\Response::clear() */ - public const CLEAN_CLEAR = 'clear'; + public const string CLEAN_CLEAR = 'clear'; /** * Do not clean the response, instead the test writer will be responsible for manually resetting the response in * between requests during one test */ - public const CLEAN_MANUAL = 'manual'; + public const string CLEAN_MANUAL = 'manual'; /** * @var string application config file */ - public $configFile; + public string $configFile; + + /** + * @var self::MAIL_CATCH|self::MAIL_IGNORE|self::MAIL_EVENT_AFTER|self::MAIL_EVENT_BEFORE method for handling mails + */ + public string $mailMethod; /** * @var string method for cleaning the response object before each request */ - public $responseCleanMethod; + public string $responseCleanMethod; /** * @var string method for cleaning the request object before each request */ - public $requestCleanMethod; + public string $requestCleanMethod; /** * @var string[] List of component names that must be recreated before each request */ - public $recreateComponents = []; + public array $recreateComponents = []; /** * This option is there primarily for backwards compatibility. @@ -94,7 +117,7 @@ final class Yii2 extends Client * * @var bool whether to recreate the whole application before each request */ - public $recreateApplication = false; + public bool $recreateApplication = false; /** * @var bool whether to close the session in between requests inside a single test, if recreateApplication is set to true @@ -108,7 +131,7 @@ final class Yii2 extends Client public string|null $applicationClass = null; /** - * @var list + * @var list */ private array $emails = []; @@ -120,14 +143,14 @@ protected function getApplication(): \yii\base\Application if (! isset(Yii::$app)) { $this->startApp(); } - return Yii::$app ?? throw new \RuntimeException('Failed to create Yii2 application'); + return Yii::$app ?? throw new RuntimeException('Failed to create Yii2 application'); } private function getWebRequest(): YiiRequest { $request = $this->getApplication()->request; if (! $request instanceof YiiRequest) { - throw new \RuntimeException('Request component is not of type ' . YiiRequest::class); + throw new RuntimeException('Request component is not of type ' . YiiRequest::class); } return $request; } @@ -150,8 +173,7 @@ public function resetApplication(bool $closeSession = true): void * Finds and logs in a user * * @internal - * @throws ConfigurationException - * @throws \RuntimeException + * @throws ConfigurationException|RuntimeException */ public function findAndLoginUser(int|string|IdentityInterface $user): void { @@ -161,15 +183,9 @@ public function findAndLoginUser(int|string|IdentityInterface $user): void throw new ConfigurationException('The user component is not configured'); } - if ($user instanceof IdentityInterface) { - $identity = $user; - } else { - // class name implementing IdentityInterface - $identityClass = $userComponent->identityClass; - $identity = $identityClass::findIdentity($user); - if ($identity === null) { - throw new \RuntimeException('User not found'); - } + $identity = $user instanceof IdentityInterface ? $user : ($userComponent->identityClass)::findIdentity($user); + if ($identity === null) { + throw new RuntimeException('User not found'); } $userComponent->login($identity); } @@ -200,10 +216,7 @@ public function getInternalDomains(): array $domains = [$this->getDomainRegex($urlManager->hostInfo)]; if ($urlManager->enablePrettyUrl) { foreach ($urlManager->rules as $rule) { - /** - * @var \yii\web\UrlRule $rule - */ - if ($rule->host !== null) { + if (isset($rule->host)) { $domains[] = $this->getDomainRegex($rule->host); } } @@ -213,7 +226,7 @@ public function getInternalDomains(): array /** * @internal - * @return list List of sent emails + * @return list List of sent emails */ public function getEmails(): array { @@ -251,7 +264,7 @@ function ($matches) use (&$parameters): string { ); } if ($template === null) { - throw new \RuntimeException("Failed to parse domain regex"); + throw new RuntimeException("Failed to parse domain regex"); } $template = preg_quote($template); $template = strtr($template, $parameters); @@ -281,7 +294,18 @@ public function startApp(?\yii\log\Logger $logger = null): void unset($config['container']); } - $config = $this->mockMailer($config); + match ($this->mailMethod) { + self::MAIL_CATCH => $config = $this->mockMailer($config), + self::MAIL_EVENT_AFTER => $config['components']['mailer']['on ' . BaseMailer::EVENT_AFTER_SEND] = function (MailEvent $event): void { + if ($event->isSuccessful) { + $this->emails[] = $event->message; + } + }, + self::MAIL_EVENT_BEFORE => $config['components']['mailer']['on ' . BaseMailer::EVENT_BEFORE_SEND] = fn (MailEvent $event) => $this->emails[] = $event->message, + self::MAIL_IGNORE => null// Do nothing + }; + + // @phpstan-ignore argument.templateType $app = Yii::createObject($config); if (! $app instanceof \yii\base\Application) { throw new ModuleConfigException($this, "Failed to initialize Yii2 app"); @@ -379,9 +403,9 @@ public function doRequest(object $request): Response $content = ob_get_clean(); if (empty($content) && ! empty($yiiResponse->content) && ! isset($yiiResponse->stream)) { - throw new \RuntimeException('No content was sent from Yii application'); + throw new RuntimeException('No content was sent from Yii application'); } elseif ($content === false) { - throw new \RuntimeException('Failed to get output buffer'); + throw new RuntimeException('Failed to get output buffer'); } return new Response($content, $yiiResponse->statusCode, $yiiResponse->getHeaders()->toArray()); @@ -450,7 +474,7 @@ protected function mockMailer(array $config): array $mailerConfig = [ 'class' => TestMailer::class, - 'callback' => function (BaseMessage $message): void { + 'callback' => function (MessageInterface $message): void { $this->emails[] = $message; }, ]; diff --git a/src/Codeception/Lib/Connector/Yii2/Logger.php b/src/Codeception/Lib/Connector/Yii2/Logger.php index 69ebd4e..2443c39 100644 --- a/src/Codeception/Lib/Connector/Yii2/Logger.php +++ b/src/Codeception/Lib/Connector/Yii2/Logger.php @@ -5,6 +5,7 @@ namespace Codeception\Lib\Connector\Yii2; use Codeception\Util\Debug; +use SplQueue; use yii\base\Exception as YiiException; use yii\helpers\VarDumper; use yii\log\Logger as YiiLogger; @@ -12,9 +13,9 @@ final class Logger extends YiiLogger { /** - * @var \SplQueue + * @var SplQueue */ - private \SplQueue $logQueue; + private SplQueue $logQueue; /** * @param array $config @@ -24,7 +25,7 @@ public function __construct( array $config = [] ) { parent::__construct($config); - $this->logQueue = new \SplQueue(); + $this->logQueue = new SplQueue(); } public function init(): void @@ -69,7 +70,7 @@ public function log($message, $level, $category = 'application'): void public function getAndClearLog(): string { $logs = iterator_to_array($this->logQueue); - $this->logQueue = new \SplQueue(); + $this->logQueue = new SplQueue(); return implode(PHP_EOL, $logs) . PHP_EOL; } } diff --git a/src/Codeception/Lib/Connector/Yii2/TestMailer.php b/src/Codeception/Lib/Connector/Yii2/TestMailer.php index 89b9bca..78d8908 100644 --- a/src/Codeception/Lib/Connector/Yii2/TestMailer.php +++ b/src/Codeception/Lib/Connector/Yii2/TestMailer.php @@ -6,10 +6,11 @@ use Closure; use yii\mail\BaseMailer; +use yii\symfonymailer\Message; final class TestMailer extends BaseMailer { - public $messageClass = \yii\symfonymailer\Message::class; + public $messageClass = Message::class; public Closure $callback; diff --git a/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php b/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php index e93ac44..1322f86 100644 --- a/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php +++ b/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php @@ -29,7 +29,7 @@ final class TransactionForcer extends ConnectionWatcher private array $transactions = []; public function __construct( - private bool $ignoreCollidingDSN + private readonly bool $ignoreCollidingDSN ) { parent::__construct(); } @@ -45,12 +45,12 @@ protected function connectionOpened(Connection $connection): void $key = md5( json_encode( [ - 'dsn' => $connection->dsn, - 'user' => $connection->username, - 'pass' => $connection->password, - 'attributes' => $connection->attributes, - 'emulatePrepare' => $connection->emulatePrepare, - 'charset' => $connection->charset, + 'dsn' => $connection->dsn, + 'user' => $connection->username, + 'pass' => $connection->password, + 'attributes' => $connection->attributes, + 'emulatePrepare' => $connection->emulatePrepare, + 'charset' => $connection->charset, ], JSON_THROW_ON_ERROR ) @@ -87,9 +87,6 @@ protected function connectionOpened(Connection $connection): void public function rollbackAll(): void { - /** - * @var Transaction $transaction - */ foreach ($this->transactions as $transaction) { if ($transaction->db->isActive) { $transaction->rollBack(); diff --git a/src/Codeception/Module/Yii2.php b/src/Codeception/Module/Yii2.php index 0da51e5..3a19cf8 100644 --- a/src/Codeception/Module/Yii2.php +++ b/src/Codeception/Module/Yii2.php @@ -15,6 +15,7 @@ use Codeception\Lib\Interfaces\ActiveRecord; use Codeception\Lib\Interfaces\PartedModule; use Codeception\TestInterface; +use Exception; use PHPUnit\Framework\Assert; use ReflectionClass; use RuntimeException; @@ -23,7 +24,6 @@ use yii\db\ActiveQueryInterface; use yii\db\ActiveRecordInterface; use yii\helpers\Url; -use yii\mail\BaseMessage; use yii\mail\MessageInterface; use yii\test\Fixture; use yii\web\IdentityInterface; @@ -88,6 +88,11 @@ * changes will get discarded. * * `recreateApplication` - (default: `false`) whether to recreate the whole * application before each request + * * `mailMethod` - (default: `catch`) Method for handling email via the 'mailer' + * component. `ignore` will not do anything with mail, this means mails are not + * inspectable by the test runner, using `before` or `after` will use mailer + * events; making the mails inspectable but also allowing your default mail + * handling to work * * You can use this module by setting params in your `functional.suite.yml`: * @@ -173,45 +178,37 @@ * Stability: **stable** * * @phpstan-type ModuleConfig array{ - * fixturesMethod: string, - * cleanup: bool, - * ignoreCollidingDSN: bool, - * transaction: bool|null, - * entryScript: string, - * entryUrl: string, - * configFile: string|null, - * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * recreateComponents: list, - * recreateApplication: bool, - * closeSessionOnRecreateApplication: bool, - * applicationClass: class-string<\yii\base\Application>|null - * } - * - * @phpstan-type ValidConfig array{ - * fixturesMethod: string, - * cleanup: bool, - * ignoreCollidingDSN: bool, - * transaction: bool|null, - * entryScript: string, - * entryUrl: string, - * configFile: string, - * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * recreateComponents: list, - * recreateApplication: bool, - * closeSessionOnRecreateApplication: bool, - * applicationClass: class-string<\yii\base\Application>|null + * configFile: string|null, + * fixturesMethod: string, + * cleanup: bool, + * ignoreCollidingDSN: bool, + * transaction: bool|null, + * entryScript: string, + * entryUrl: string, + * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * mailMethod: Yii2Connector::MAIL_CATCH|Yii2Connector::MAIL_IGNORE|Yii2Connector::MAIL_EVENT_AFTER|Yii2Connector::MAIL_EVENT_BEFORE, + * recreateComponents: list, + * recreateApplication: bool, + * closeSessionOnRecreateApplication: bool, + * applicationClass: class-string<\yii\base\Application>|null * } + * * @phpstan-type ClientConfig array{ - * configFile: string, - * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, - * recreateComponents: list, - * recreateApplication: bool, - * closeSessionOnRecreateApplication: bool, - * applicationClass: class-string<\yii\base\Application>|null - * } + * configFile: string, + * responseCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * requestCleanMethod: Yii2Connector::CLEAN_CLEAR|Yii2Connector::CLEAN_MANUAL|Yii2Connector::CLEAN_RECREATE, + * mailMethod: Yii2Connector::MAIL_CATCH|Yii2Connector::MAIL_IGNORE|Yii2Connector::MAIL_EVENT_AFTER|Yii2Connector::MAIL_EVENT_BEFORE, + * recreateComponents: list, + * recreateApplication: bool, + * closeSessionOnRecreateApplication: bool, + * applicationClass: class-string<\yii\base\Application>|null + * } + * + * @phpstan-type ValidConfig (ModuleConfig & array{ + * transaction: bool|null, + * configFile: string + * }) */ final class Yii2 extends Framework implements ActiveRecord, PartedModule { @@ -237,6 +234,7 @@ final class Yii2 extends Framework implements ActiveRecord, PartedModule 'requestCleanMethod' => Yii2Connector::CLEAN_RECREATE, 'recreateComponents' => [], 'recreateApplication' => false, + 'mailMethod' => Yii2Connector::MAIL_CATCH, 'closeSessionOnRecreateApplication' => true, 'applicationClass' => null, ]; @@ -258,7 +256,7 @@ final class Yii2 extends Framework implements ActiveRecord, PartedModule */ private array $server; - private Logger $yiiLogger; + private null|Logger $yiiLogger = null; private function getClient(): Yii2Connector { @@ -279,7 +277,9 @@ public function _initialize(): void $this->defineConstants(); $this->server = $_SERVER; - $this->initServerGlobal(); + // Adds the required server params. Note this is done separately from the request cycle since someone might call + // `Url::to` before doing a request, which would instantiate the request component with incorrect server params. + $_SERVER = [...$_SERVER, ...$this->getServerParams()]; } /** @@ -292,31 +292,32 @@ protected function onReconfigure(): void $this->getClient()->resetApplication(); $this->validateConfig(); $this->configureClient($this->config); - $this->yiiLogger->getAndClearLog(); + $this->yiiLogger?->getAndClearLog(); $this->getClient()->startApp($this->yiiLogger); } /** - * Adds the required server params. - * Note this is done separately from the request cycle since someone might call - * `Url::to` before doing a request, which would instantiate the request component with incorrect server params. + * @return array{ + * SCRIPT_FILENAME: string, + * SCRIPT_NAME: string, + * SERVER_NAME: string, + * SERVER_PORT: string|int, + * HTTPS: bool + * } */ - private function initServerGlobal(): void + private function getServerParams(): array { $entryUrl = $this->config['entryUrl']; $parsedUrl = parse_url($entryUrl); $entryFile = $this->config['entryScript'] ?: basename($entryUrl); $entryScript = $this->config['entryScript'] ?: ($parsedUrl['path'] ?? ''); - $_SERVER = array_merge( - $_SERVER, - [ + return [ 'SCRIPT_FILENAME' => $entryFile, 'SCRIPT_NAME' => $entryScript, 'SERVER_NAME' => $parsedUrl['host'] ?? '', 'SERVER_PORT' => $parsedUrl['port'] ?? '80', 'HTTPS' => isset($parsedUrl['scheme']) && $parsedUrl['scheme'] === 'https', - ] - ); + ]; } /** @@ -338,17 +339,24 @@ protected function validateConfig(): void "The application config file does not exist: " . $pathToConfig, ); } - $validMethods = implode(", ", Yii2Connector::CLEAN_METHODS); + $validCleanMethods = implode(", ", Yii2Connector::CLEAN_METHODS); if (! in_array($this->config['responseCleanMethod'], Yii2Connector::CLEAN_METHODS, true)) { throw new ModuleConfigException( self::class, - "The response clean method must be one of: " . $validMethods, + "The response clean method must be one of: " . $validCleanMethods, + ); + } + $validMailMethods = implode(", ", Yii2Connector::MAIL_METHODS); + if (! in_array($this->config['mailMethod'], Yii2Connector::MAIL_METHODS, true)) { + throw new ModuleConfigException( + self::class, + "The mail method must be one of: " . $validMailMethods ); } if (! in_array($this->config['requestCleanMethod'], Yii2Connector::CLEAN_METHODS, true)) { throw new ModuleConfigException( self::class, - "The request clean method must be one of: " . $validMethods, + "The request clean method must be one of: " . $validCleanMethods, ); } } @@ -365,6 +373,7 @@ private function configureClient(array $settings): void $client->recreateApplication = $settings['recreateApplication']; $client->closeSessionOnRecreateApplication = $settings['closeSessionOnRecreateApplication']; $client->applicationClass = $settings['applicationClass']; + $client->mailMethod = $settings['mailMethod']; $client->resetApplication(); } @@ -373,19 +382,7 @@ private function configureClient(array $settings): void */ protected function recreateClient(): void { - $entryUrl = $this->config['entryUrl']; - $parsedUrl = parse_url($entryUrl); - $entryFile = $this->config['entryScript'] ?: basename($entryUrl); - $entryScript = $this->config['entryScript'] ?: ($parsedUrl['path'] ?? ''); - $this->client = new Yii2Connector( - [ - 'SCRIPT_FILENAME' => $entryFile, - 'SCRIPT_NAME' => $entryScript, - 'SERVER_NAME' => $parsedUrl['host'] ?? '', - 'SERVER_PORT' => $parsedUrl['port'] ?? '80', - 'HTTPS' => isset($parsedUrl['scheme']) && $parsedUrl['scheme'] === 'https', - ] - ); + $this->client = new Yii2Connector($this->getServerParams()); $this->validateConfig(); $this->configureClient($this->config); } @@ -459,12 +456,12 @@ public function _after(TestInterface $test): void } /** - * @param \Exception $fail + * @param Exception $fail */ public function _failed(TestInterface $test, $fail): void { - $log = $this->yiiLogger->getAndClearLog(); - if ($log !== '') { + $log = $this->yiiLogger?->getAndClearLog(); + if (isset($log) && $log !== '') { $test->getMetadata()->addReport('yii-log', $log); } @@ -799,7 +796,7 @@ public function dontSeeEmailIsSent(): void * ``` * * @part email - * @return list List of sent emails + * @return list List of sent emails * @throws \Codeception\Exception\ModuleException */ public function grabSentEmails(): array @@ -823,7 +820,7 @@ public function grabSentEmails(): array * * @part email */ - public function grabLastSentEmail(): BaseMessage|null + public function grabLastSentEmail(): MessageInterface|null { $this->seeEmailIsSent(); $messages = $this->grabSentEmails(); diff --git a/tests/Yii.stub b/tests/Yii.stub index bbfd44a..87cd9aa 100644 --- a/tests/Yii.stub +++ b/tests/Yii.stub @@ -95,4 +95,12 @@ namespace yii\web { public function setScriptFile($path): void {} } + interface UrlRuleInterface{} + class UrlManager { + /** + * @var UrlRuleInterface[] $rules; + */ + public array $rules = []; + } + }