fix(scheduler): skip test directories in ScheduledActionScanner
Some checks failed
CI / PHP 8.4 (push) Failing after 2m1s
CI / PHP 8.3 (push) Failing after 2m8s

Test files inside module Tests/ directories (e.g. app/Mod/Lem/Tests/)
extend Tests\TestCase which isn't available in production without dev
dependencies. The scanner now skips /Tests/ directories and *Test.php
files, and wraps class_exists() in try/catch for defence in depth.

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-12 15:45:45 +00:00
parent 7db3637985
commit d02f4361e3
3 changed files with 59 additions and 1 deletions

View file

@ -52,9 +52,26 @@ class ScheduledActionScanner
continue;
}
// Skip test directories — test files extend base classes
// that aren't available without dev dependencies.
// Convention: module test dirs use capital "Tests/" (e.g. app/Mod/Lem/Tests/).
if (preg_match('#[/\\\\]Tests[/\\\\]#', $file->getPathname())
|| str_ends_with($file->getBasename(), 'Test.php')) {
continue;
}
$class = $this->classFromFile($file->getPathname());
if ($class === null || ! class_exists($class)) {
if ($class === null) {
continue;
}
try {
if (! class_exists($class)) {
continue;
}
} catch (\Throwable) {
// Class may reference unavailable dependencies (e.g. dev-only)
continue;
}

View file

@ -70,4 +70,21 @@ class ScheduledActionScannerTest extends TestCase
$results = $this->scanner->scan(['/nonexistent/path']);
$this->assertEmpty($results);
}
public function test_scan_skips_test_directories(): void
{
$results = $this->scanner->scan([
dirname(__DIR__).'/Fixtures/Mod/Scheduled',
]);
// FakeScheduledTest is inside a Tests/ directory and should be skipped
$classes = array_keys($results);
foreach ($classes as $class) {
$this->assertStringNotContainsString('FakeScheduledTest', $class);
}
// But real actions are still discovered
$this->assertArrayHasKey(EveryMinuteAction::class, $results);
$this->assertArrayHasKey(DailyAction::class, $results);
}
}

View file

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace Core\Tests\Fixtures\Mod\Scheduled\Tests;
use Core\Actions\Action;
use Core\Actions\Scheduled;
/**
* This file lives inside a Tests/ directory to verify that the
* scanner skips test directories. Despite having #[Scheduled],
* it should never be discovered.
*/
#[Scheduled(frequency: 'everyMinute')]
class FakeScheduledTest
{
use Action;
public function handle(): string
{
return 'should-not-be-discovered';
}
}