Skip to content

Commit 9258f16

Browse files
Add ConfigResource for PHPUnit configuration
- Provide PHPUnit configuration as MCP resource - Include project root, config file path, and test directories - Extract bootstrap file from configuration - Use TOON format for token-efficient output
1 parent 9e53fda commit 9258f16

5 files changed

Lines changed: 286 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CHANGELOG
2+
=========
3+
4+
0.1
5+
---
6+
7+
* Add extension

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,4 @@ MIT License - see [LICENSE](LICENSE) file for details.
5858

5959
---
6060

61-
*Built with the MatesOfMate community*
61+
*"Because every Mate needs Mates"*

config/config.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111

1212
use MatesOfMate\Common\Process\ProcessExecutor;
13+
use MatesOfMate\PHPUnitExtension\Capability\ConfigResource;
1314
use MatesOfMate\PHPUnitExtension\Capability\ListTestsTool;
1415
use MatesOfMate\PHPUnitExtension\Capability\RunFileTool;
1516
use MatesOfMate\PHPUnitExtension\Capability\RunMethodTool;
@@ -49,4 +50,7 @@
4950
$services->set(RunFileTool::class);
5051
$services->set(RunMethodTool::class);
5152
$services->set(ListTestsTool::class);
53+
54+
// Resources - automatically discovered by #[McpResource] attribute
55+
$services->set(ConfigResource::class);
5256
};

src/Capability/ConfigResource.php

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the MatesOfMate Organisation.
5+
*
6+
* (c) Johannes Wachter <johannes@sulu.io>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace MatesOfMate\PHPUnitExtension\Capability;
13+
14+
use MatesOfMate\PHPUnitExtension\Config\ConfigurationDetector;
15+
use Mcp\Capability\Attribute\McpResource;
16+
17+
/**
18+
* Provides PHPUnit configuration information as an MCP resource.
19+
*
20+
* @author Johannes Wachter <johannes@sulu.io>
21+
*/
22+
class ConfigResource
23+
{
24+
public function __construct(
25+
private readonly ConfigurationDetector $configDetector,
26+
) {
27+
}
28+
29+
/**
30+
* @return array{uri: string, mimeType: string, text: string}
31+
*/
32+
#[McpResource(
33+
uri: 'phpunit://config',
34+
name: 'phpunit-configuration',
35+
description: 'PHPUnit project configuration details in TOON format. Provides project root, config file path, test directories, bootstrap file, and full config content. Use for: understanding project setup, checking test configuration, locating test directories, troubleshooting configuration issues.',
36+
mimeType: 'text/plain',
37+
)]
38+
public function getConfiguration(): array
39+
{
40+
$projectRoot = getcwd();
41+
if (false === $projectRoot) {
42+
throw new \RuntimeException('Unable to determine current working directory');
43+
}
44+
45+
$configPath = $this->configDetector->detect($projectRoot);
46+
47+
$data = [
48+
'project_root' => $projectRoot,
49+
'config_file' => $configPath,
50+
'config_exists' => null !== $configPath,
51+
];
52+
53+
if (null !== $configPath) {
54+
$data['test_directories'] = $this->configDetector->getTestDirectories();
55+
$data['bootstrap_file'] = $this->extractBootstrapFile($configPath);
56+
$data['config_content'] = file_exists($configPath) ? file_get_contents($configPath) : null;
57+
}
58+
59+
return [
60+
'uri' => 'phpunit://config',
61+
'mimeType' => 'text/plain',
62+
'text' => toon($data),
63+
];
64+
}
65+
66+
private function extractBootstrapFile(string $configPath): ?string
67+
{
68+
$xml = @simplexml_load_file($configPath);
69+
if (false === $xml) {
70+
return null;
71+
}
72+
73+
$bootstrap = $xml->attributes()['bootstrap'] ?? null;
74+
75+
return null !== $bootstrap ? (string) $bootstrap : null;
76+
}
77+
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the MatesOfMate Organisation.
5+
*
6+
* (c) Johannes Wachter <johannes@sulu.io>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace MatesOfMate\PHPUnitExtension\Tests\Unit\Capability;
13+
14+
use MatesOfMate\PHPUnitExtension\Capability\ConfigResource;
15+
use MatesOfMate\PHPUnitExtension\Config\ConfigurationDetector;
16+
use PHPUnit\Framework\TestCase;
17+
18+
class ConfigResourceTest extends TestCase
19+
{
20+
private string $tempDir;
21+
22+
protected function setUp(): void
23+
{
24+
$this->tempDir = sys_get_temp_dir().'/phpunit-config-resource-test-'.uniqid();
25+
mkdir($this->tempDir);
26+
}
27+
28+
protected function tearDown(): void
29+
{
30+
if (is_dir($this->tempDir)) {
31+
$files = glob($this->tempDir.'/*');
32+
if (false !== $files) {
33+
array_map(unlink(...), $files);
34+
}
35+
rmdir($this->tempDir);
36+
}
37+
}
38+
39+
public function testGetConfigurationWithExistingConfig(): void
40+
{
41+
$configPath = $this->tempDir.'/phpunit.xml';
42+
file_put_contents($configPath, <<<'XML'
43+
<?xml version="1.0" encoding="UTF-8"?>
44+
<phpunit bootstrap="vendor/autoload.php">
45+
<testsuites>
46+
<testsuite name="default">
47+
<directory>tests</directory>
48+
</testsuite>
49+
</testsuites>
50+
</phpunit>
51+
XML
52+
);
53+
54+
$originalCwd = getcwd();
55+
chdir($this->tempDir);
56+
57+
try {
58+
$detector = new ConfigurationDetector($this->tempDir);
59+
$resource = new ConfigResource($detector);
60+
61+
$result = $resource->getConfiguration();
62+
63+
$this->assertIsArray($result);
64+
$this->assertArrayHasKey('uri', $result);
65+
$this->assertArrayHasKey('mimeType', $result);
66+
$this->assertArrayHasKey('text', $result);
67+
68+
$this->assertSame('phpunit://config', $result['uri']);
69+
$this->assertSame('text/plain', $result['mimeType']);
70+
$this->assertIsString($result['text']);
71+
72+
// Verify the text contains expected data
73+
$this->assertStringContainsString('project_root', $result['text']);
74+
$this->assertStringContainsString('config_file', $result['text']);
75+
$this->assertStringContainsString('config_exists', $result['text']);
76+
$this->assertStringContainsString('test_directories', $result['text']);
77+
$this->assertStringContainsString('bootstrap_file', $result['text']);
78+
$this->assertStringContainsString('config_content', $result['text']);
79+
$this->assertStringContainsString('vendor/autoload.php', $result['text']);
80+
} finally {
81+
if (false !== $originalCwd) {
82+
chdir($originalCwd);
83+
}
84+
}
85+
}
86+
87+
public function testGetConfigurationWithoutConfig(): void
88+
{
89+
$originalCwd = getcwd();
90+
chdir($this->tempDir);
91+
92+
try {
93+
$detector = new ConfigurationDetector($this->tempDir);
94+
$resource = new ConfigResource($detector);
95+
96+
$result = $resource->getConfiguration();
97+
98+
$this->assertIsArray($result);
99+
$this->assertArrayHasKey('uri', $result);
100+
$this->assertArrayHasKey('mimeType', $result);
101+
$this->assertArrayHasKey('text', $result);
102+
103+
$this->assertSame('phpunit://config', $result['uri']);
104+
$this->assertSame('text/plain', $result['mimeType']);
105+
$this->assertIsString($result['text']);
106+
107+
// Verify the text contains expected data
108+
$this->assertStringContainsString('project_root', $result['text']);
109+
$this->assertStringContainsString('config_file', $result['text']);
110+
$this->assertStringContainsString('config_exists', $result['text']);
111+
} finally {
112+
if (false !== $originalCwd) {
113+
chdir($originalCwd);
114+
}
115+
}
116+
}
117+
118+
public function testReturnStructure(): void
119+
{
120+
$originalCwd = getcwd();
121+
chdir($this->tempDir);
122+
123+
try {
124+
$detector = new ConfigurationDetector($this->tempDir);
125+
$resource = new ConfigResource($detector);
126+
127+
$result = $resource->getConfiguration();
128+
129+
$this->assertIsArray($result);
130+
$this->assertCount(3, $result);
131+
$this->assertArrayHasKey('uri', $result);
132+
$this->assertArrayHasKey('mimeType', $result);
133+
$this->assertArrayHasKey('text', $result);
134+
} finally {
135+
if (false !== $originalCwd) {
136+
chdir($originalCwd);
137+
}
138+
}
139+
}
140+
141+
public function testToonFormatting(): void
142+
{
143+
$originalCwd = getcwd();
144+
chdir($this->tempDir);
145+
146+
try {
147+
$detector = new ConfigurationDetector($this->tempDir);
148+
$resource = new ConfigResource($detector);
149+
150+
$result = $resource->getConfiguration();
151+
152+
// TOON format should be compact and structured
153+
$this->assertIsString($result['text']);
154+
$this->assertNotEmpty($result['text']);
155+
156+
// TOON format produces human-readable text, not JSON
157+
$this->assertStringNotContainsString('{', $result['text']);
158+
$this->assertStringNotContainsString('[', $result['text']);
159+
} finally {
160+
if (false !== $originalCwd) {
161+
chdir($originalCwd);
162+
}
163+
}
164+
}
165+
166+
public function testBootstrapExtraction(): void
167+
{
168+
$configPath = $this->tempDir.'/phpunit.xml';
169+
file_put_contents($configPath, <<<'XML'
170+
<?xml version="1.0" encoding="UTF-8"?>
171+
<phpunit bootstrap="tests/bootstrap.php">
172+
<testsuites>
173+
<testsuite name="default">
174+
<directory>tests</directory>
175+
</testsuite>
176+
</testsuites>
177+
</phpunit>
178+
XML
179+
);
180+
181+
$originalCwd = getcwd();
182+
chdir($this->tempDir);
183+
184+
try {
185+
$detector = new ConfigurationDetector($this->tempDir);
186+
$resource = new ConfigResource($detector);
187+
188+
$result = $resource->getConfiguration();
189+
190+
$this->assertStringContainsString('tests/bootstrap.php', $result['text']);
191+
} finally {
192+
if (false !== $originalCwd) {
193+
chdir($originalCwd);
194+
}
195+
}
196+
}
197+
}

0 commit comments

Comments
 (0)