Skip to content
This repository was archived by the owner on Feb 17, 2026. It is now read-only.

Commit 3b97c36

Browse files
committed
tests: add tests that actually scans a local plugin for attributes
1 parent 39d3566 commit 3b97c36

File tree

11 files changed

+220
-3
lines changed

11 files changed

+220
-3
lines changed

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"autoload-dev": {
3131
"psr-4": {
3232
"TestApp\\": "tests/test_app/src/",
33+
"TestLocalPlugin\\": "tests/test_app/plugins/TestLocalPlugin/src/",
3334
"AttributeRegistry\\Test\\": "tests/"
3435
}
3536
},

src/Service/PluginPathResolver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ class PluginPathResolver
2929
*/
3030
public function getEnabledPluginPaths(): array
3131
{
32-
$allPlugins = PluginConfig::getAppConfig();
3332
$paths = [];
33+
$allPlugins = PluginConfig::getAppConfig();
3434
$pluginCollection = Plugin::getCollection();
3535

3636
foreach ($allPlugins as $name => $config) {

tests/TestCase/AttributeRegistryTest.php

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66
use AttributeRegistry\AttributeRegistry;
77
use AttributeRegistry\Collection\AttributeCollection;
88
use AttributeRegistry\Enum\AttributeTargetType;
9+
use AttributeRegistry\Service\PluginPathResolver;
910
use AttributeRegistry\Test\Data\TestAttributeArgument;
1011
use AttributeRegistry\Test\Data\TestRoute;
1112
use AttributeRegistry\Test\Data\TestWithObject;
1213
use AttributeRegistry\Test\Data\TestWithObjectArray;
14+
use AttributeRegistry\ValueObject\AttributeInfo;
1315
use Cake\Cache\Cache;
1416
use Cake\TestSuite\TestCase;
1517

@@ -29,15 +31,18 @@ protected function setUp(): void
2931
]);
3032

3133
$this->loadTestAttributes();
34+
$this->loadTestPlugins();
3235

33-
$this->registry = $this->createRegistry('attribute_test', true);
36+
// Create registry with both test data and plugin paths
37+
$this->registry = $this->createRegistryWithPlugins('attribute_test', true);
3438
}
3539

3640
protected function tearDown(): void
3741
{
3842
parent::tearDown();
3943
Cache::clear('attribute_test');
4044
Cache::drop('attribute_test');
45+
$this->clearPlugins();
4146
}
4247

4348
public function testAttributeRegistryCanBeCreated(): void
@@ -383,4 +388,51 @@ public function testObjectArgumentsPreservedThroughCaching(): void
383388
$this->assertEquals($attr1->arguments, $attr2->arguments);
384389
}
385390
}
391+
392+
public function testDiscoverIncludesLocalPluginAttributes(): void
393+
{
394+
// Debug: Check if plugin paths are being picked up
395+
$pluginPathResolver = new PluginPathResolver();
396+
$paths = $pluginPathResolver->getEnabledPluginPaths();
397+
398+
// Discover all attributes
399+
$results = $this->registry->discover();
400+
401+
// Debug: check all discovered attributes
402+
$allAttributes = $results->toList();
403+
$attributeNames = array_map(fn(AttributeInfo $attr): string => $attr->attributeName, $allAttributes);
404+
405+
// Filter for LocalPluginRoute attribute from test local plugin
406+
$localPluginAttributes = $results
407+
->attributeContains('LocalPluginRoute')
408+
->toList();
409+
410+
// Should find attributes from local plugin
411+
$this->assertNotEmpty(
412+
$localPluginAttributes,
413+
'Should discover attributes from local plugin. Plugin paths: ' . implode(', ', $paths) .
414+
'. Found attributes: ' . implode(', ', $attributeNames),
415+
);
416+
417+
// Verify attributes are from the local plugin namespace
418+
foreach ($localPluginAttributes as $attr) {
419+
$this->assertStringContainsString('TestLocalPlugin', $attr->className);
420+
}
421+
}
422+
423+
public function testDiscoverIncludesLocalPluginController(): void
424+
{
425+
// Discover all attributes from TestLocalController
426+
$results = $this->registry->discover()
427+
->classNameContains('TestLocalController')
428+
->toList();
429+
430+
// Should find the controller with attributes
431+
$this->assertNotEmpty($results, 'Should discover TestLocalController from local plugin');
432+
433+
// Verify we got both class and method attributes
434+
$targetTypes = array_unique(array_map(fn(AttributeInfo $attr) => $attr->target->type->value, $results));
435+
$this->assertContains('class', $targetTypes, 'Should have class-level attribute');
436+
$this->assertContains('method', $targetTypes, 'Should have method-level attribute');
437+
}
386438
}

tests/TestCase/AttributeRegistryTestTrait.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use AttributeRegistry\Service\AttributeParser;
99
use AttributeRegistry\Service\AttributeScanner;
1010
use AttributeRegistry\Service\PathResolver;
11+
use AttributeRegistry\Service\PluginPathResolver;
1112

1213
/**
1314
* Trait providing factory methods for common test objects.
@@ -83,6 +84,46 @@ protected function createRegistry(
8384
return new AttributeRegistry($scanner, $cache);
8485
}
8586

87+
/**
88+
* Create a new AttributeRegistry instance that includes plugin paths.
89+
*
90+
* This is useful for integration tests that need to discover attributes
91+
* from loaded plugins (including local plugins).
92+
*
93+
* @param string $cacheKey Cache configuration key
94+
* @param bool $cacheEnabled Whether caching is enabled
95+
* @param array<string, mixed> $scannerConfig Scanner configuration options
96+
*/
97+
protected function createRegistryWithPlugins(
98+
string $cacheKey,
99+
bool $cacheEnabled = false,
100+
array $scannerConfig = [],
101+
): AttributeRegistry {
102+
// Build path string including test data + all loaded plugins
103+
$pluginPathResolver = new PluginPathResolver();
104+
$pluginPaths = $pluginPathResolver->getEnabledPluginPaths();
105+
$allPaths = array_merge([$this->getTestDataPath()], $pluginPaths);
106+
$pathString = implode(PATH_SEPARATOR, $allPaths);
107+
108+
$pathResolver = new PathResolver($pathString);
109+
$parser = $this->createParser();
110+
111+
// Use src/**/*.php for plugins, *.php for test data
112+
$defaultConfig = [
113+
'paths' => ['*.php', 'src/**/*.php'],
114+
'exclude_paths' => [],
115+
];
116+
117+
$scanner = new AttributeScanner(
118+
$parser,
119+
$pathResolver,
120+
$scannerConfig + $defaultConfig,
121+
);
122+
$cache = $this->createCache($cacheKey, $cacheEnabled);
123+
124+
return new AttributeRegistry($scanner, $cache);
125+
}
126+
86127
/**
87128
* Get the path to the test data directory.
88129
*/
@@ -111,4 +152,12 @@ protected function loadTestAttributes(): void
111152
{
112153
require_once $this->getTestDataPath() . '/TestAttributes.php';
113154
}
155+
156+
/**
157+
* Load test plugins including local plugin.
158+
*/
159+
protected function loadTestPlugins(): void
160+
{
161+
$this->loadPlugins(['TestLocalPlugin']);
162+
}
114163
}

tests/TestCase/Service/PluginPathResolverTest.php

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,22 @@ class PluginPathResolverTest extends TestCase
1919
protected function setUp(): void
2020
{
2121
parent::setUp();
22+
23+
// Load test local plugin using CakePHP test helper
24+
$this->loadPlugins(['TestLocalPlugin']);
25+
2226
$this->resolver = new PluginPathResolver();
2327
}
2428

29+
/**
30+
* tearDown method
31+
*/
32+
protected function tearDown(): void
33+
{
34+
parent::tearDown();
35+
$this->clearPlugins();
36+
}
37+
2538
/**
2639
* Test getEnabledPluginPaths returns paths for actual loaded plugins
2740
*/
@@ -71,4 +84,40 @@ public function testGetEnabledPluginPathsOnlyIncludesLoadedPlugins(): void
7184
$this->assertNotEmpty($paths, 'Should have at least one plugin path');
7285
}
7386
}
87+
88+
/**
89+
* Test that local plugins without packagePath are included
90+
*/
91+
public function testGetEnabledPluginPathsIncludesLocalPlugins(): void
92+
{
93+
// Get paths from resolver
94+
$paths = $this->resolver->getEnabledPluginPaths();
95+
96+
// Verify local plugin path is included
97+
$localPluginPath = ROOT . DS . 'plugins' . DS . 'TestLocalPlugin' . DS;
98+
$this->assertContains($localPluginPath, $paths, 'Local plugin path should be included');
99+
100+
// Verify the path actually exists
101+
$this->assertDirectoryExists($localPluginPath, 'Local plugin directory should exist');
102+
}
103+
104+
/**
105+
* Test that local plugin attributes are discoverable
106+
*/
107+
public function testLocalPluginAttributesAreDiscoverable(): void
108+
{
109+
// Get paths and verify plugin is included
110+
$paths = $this->resolver->getEnabledPluginPaths();
111+
$localPluginPath = ROOT . DS . 'plugins' . DS . 'TestLocalPlugin' . DS;
112+
113+
$this->assertContains($localPluginPath, $paths);
114+
115+
// Verify attribute file exists in local plugin
116+
$attributeFile = $localPluginPath . 'src' . DS . 'Attribute' . DS . 'LocalPluginRoute.php';
117+
$this->assertFileExists($attributeFile, 'Attribute file should exist in local plugin');
118+
119+
// Verify controller with attributes exists
120+
$controllerFile = $localPluginPath . 'src' . DS . 'Controller' . DS . 'TestLocalController.php';
121+
$this->assertFileExists($controllerFile, 'Controller file should exist in local plugin');
122+
}
74123
}

tests/bootstrap.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,15 @@
3737
'defaultLocale' => 'en_US',
3838
'fullBaseUrl' => 'http://localhost',
3939
'paths' => [
40-
'plugins' => [ROOT . 'plugins' . DS],
40+
'plugins' => [ROOT . DS . 'plugins' . DS],
4141
'templates' => [ROOT . DS . 'templates' . DS],
4242
'locales' => [ROOT . DS . 'resources' . DS . 'locales' . DS],
4343
],
4444
]);
4545

46+
// Register test local plugin path
47+
Configure::write('plugins.TestLocalPlugin', ROOT . DS . 'plugins' . DS . 'TestLocalPlugin' . DS);
48+
4649
Configure::write('debug', true);
4750
Chronos::setTestNow(Chronos::now());
4851

tests/config/plugins.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
/**
3+
* Test plugins configuration
4+
*/
5+
return [
6+
'TestLocalPlugin' => [],
7+
];
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace TestLocalPlugin\Attribute;
5+
6+
use Attribute;
7+
8+
/**
9+
* Test attribute for local plugin testing
10+
*/
11+
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
12+
class LocalPluginRoute
13+
{
14+
public function __construct(
15+
public readonly string $path,
16+
public readonly ?string $method = null,
17+
public readonly ?string $name = null,
18+
) {
19+
}
20+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace TestLocalPlugin\Controller;
5+
6+
use Cake\Controller\Controller;
7+
use TestLocalPlugin\Attribute\LocalPluginRoute;
8+
9+
/**
10+
* Test controller with attributes in local plugin
11+
*/
12+
#[LocalPluginRoute('/local-test', name: 'local_test')]
13+
class TestLocalController extends Controller
14+
{
15+
#[LocalPluginRoute('/action', method: 'GET')]
16+
public function testAction(): void
17+
{
18+
}
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace TestLocalPlugin;
5+
6+
use Cake\Core\BasePlugin;
7+
8+
/**
9+
* Test local plugin for testing plugin path resolution
10+
*/
11+
class TestLocalPlugin extends BasePlugin
12+
{
13+
}

0 commit comments

Comments
 (0)