Skip to content

Commit 1430ae2

Browse files
author
klapaudius
committed
Refactor test cases and enhance MCP tool command logic
Replaced redundant mocks and improved method naming for clarity in test cases. Refactored `execute` logic to ensure proper initialization of input and output handling. Added new tests to validate tool selection and input data handling scenarios, and improved tool listing functionality. Migrated configuration management to a compiler pass, simplifying the bundle setup.
1 parent 32a77bc commit 1430ae2

File tree

4 files changed

+109
-38
lines changed

4 files changed

+109
-38
lines changed

src/Command/TestMcpToolCommand.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ protected function configure(): void
5656
*/
5757
public function execute(InputInterface $input, OutputInterface $output): int
5858
{
59-
$this->input = $input;
60-
$this->io = new SymfonyStyle($input, $output);
59+
$this->input ??= $input;
60+
$this->io ??= new SymfonyStyle($input, $output);
6161

6262
// List all tools if --list option is provided
6363
return $this->input->getOption('list')
@@ -283,7 +283,7 @@ public function askForInputData(array $schema): ?array
283283
/**
284284
* List all available tools.
285285
*/
286-
public function listAllTools(): int
286+
private function listAllTools(): int
287287
{
288288
$configuredTools = $this->container->getParameter('klp_mcp_server.tools');
289289

src/KlpMcpServerBundle.php

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,6 @@
1111

1212
class KlpMcpServerBundle extends Bundle
1313
{
14-
public function boot(): void
15-
{
16-
parent::boot();
17-
$configPath = $this->container->getParameter('kernel.project_dir').'/config/packages/klp_mcp_server.yaml';
18-
$filesystem = new Filesystem;
19-
20-
// Check if the file already exists
21-
if (! $filesystem->exists($configPath)) {
22-
$defaultConfig = __DIR__.'/Resources/config/packages/klp_mcp_server.yaml';
23-
$filesystem->copy($defaultConfig, $configPath);
24-
}
25-
}
26-
27-
public function configurePackage(Package $package): void
28-
{
29-
/*
30-
* This class is a Package Service Provider
31-
*
32-
* More info: https://github.com/spatie/symfony-package-tools
33-
*/
34-
$package
35-
->name('symfony-mcp-server')
36-
->hasConfigFile('mcp-server')
37-
->hasCommands([
38-
MakeMcpToolCommand::class,
39-
TestMcpToolCommand::class,
40-
]);
41-
}
42-
4314
public function build(ContainerBuilder $container): void
4415
{
4516
parent::build($container);

tests/Command/TestMcpToolCommandTest.php

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use PHPUnit\Framework\Attributes\Small;
1111
use PHPUnit\Framework\MockObject\MockObject;
1212
use PHPUnit\Framework\TestCase;
13-
use stdClass;
13+
use Symfony\Component\Console\Application;
1414
use Symfony\Component\Console\Command\Command;
1515
use Symfony\Component\Console\Input\InputInterface;
1616
use Symfony\Component\Console\Output\OutputInterface;
@@ -38,15 +38,15 @@ protected function setUp(): void
3838
$this->ioMock = $this->createMock(SymfonyStyle::class);
3939

4040
$this->command = new TestMcpToolCommand($this->containerMock);
41-
$this->command->setApplication($this->createMock(\Symfony\Component\Console\Application::class));
41+
$this->command->setApplication($this->createMock(Application::class));
4242
$this->injectPrivateProperty($this->command, 'input', $this->inputMock);
4343
$this->injectPrivateProperty($this->command, 'io', $this->ioMock);
4444
}
4545

4646
/**
4747
* Tests that an exception is thrown when no tool is provided
4848
*/
49-
public function test_get_tool_instance_no_tool_provided_throws_exception(): void
49+
public function test_get_tool_instance_no_tool_provided_and_no_tool_configured_throws_exception(): void
5050
{
5151
$this->inputMock
5252
->method('getArgument')
@@ -59,6 +59,32 @@ public function test_get_tool_instance_no_tool_provided_throws_exception(): void
5959
$this->invokeGetToolInstanceMethod();
6060
}
6161

62+
/**
63+
* Tests that an exception is thrown when no tool is provided
64+
*/
65+
public function test_get_tool_instance_no_tool_provided_ask_choice_from_configured_tools(): void
66+
{
67+
$this->containerMock
68+
->method('getParameter')
69+
->with('klp_mcp_server.tools')
70+
->willReturn([HelloWorldTool::class]);
71+
$this->containerMock
72+
->method('get')
73+
->with(HelloWorldTool::class)
74+
->willReturn(new HelloWorldTool);
75+
$this->inputMock
76+
->method('getArgument')
77+
->with('tool')
78+
->willReturn(null);
79+
80+
$this->ioMock
81+
->expects($this->once())
82+
->method('choice')
83+
->with('Select a tool to test', ['hello-world ('.HelloWorldTool::class.')']);
84+
85+
$this->invokeGetToolInstanceMethod();
86+
}
87+
6288
/**
6389
* Tests that a tool instance is returned when a valid class name is provided
6490
*/
@@ -412,6 +438,12 @@ private function invokePrivateMethod(string $methodName, array $args = []): mixe
412438
*/
413439
public function test_list_all_tools_when_no_tools_are_configured_displays_warning(): void
414440
{
441+
$this->inputMock
442+
->expects($this->once())
443+
->method('getOption')
444+
->with('list')
445+
->willReturn('list');
446+
415447
$this->containerMock
416448
->method('getParameter')
417449
->with('klp_mcp_server.tools')
@@ -422,14 +454,19 @@ public function test_list_all_tools_when_no_tools_are_configured_displays_warnin
422454
->method('warning')
423455
->with('No MCP tools are configured. Add tools in config/package/klp-mcp-server.yaml');
424456

425-
$this->assertEquals(Command::SUCCESS, $this->command->listAllTools());
457+
$this->assertEquals(Command::SUCCESS, $this->command->execute($this->inputMock, $this->outputMock));;
426458
}
427459

428460
/**
429461
* Tests that when valid tools are present, they are displayed in a table.
430462
*/
431463
public function test_list_all_tools_when_valid_tools_present_displays_table(): void
432464
{
465+
$this->inputMock
466+
->expects($this->once())
467+
->method('getOption')
468+
->with('list')
469+
->willReturn('list');
433470
$tools = [HelloWorldTool::class, VersionCheckTool::class];
434471
$toolMocks = [
435472
['name' => 'Tool1', 'class' => HelloWorldTool::class, 'description' => 'This is tool 1'],
@@ -453,14 +490,19 @@ public function test_list_all_tools_when_valid_tools_present_displays_table(): v
453490
->method('table')
454491
->with(['Name', 'Class', 'Description'], $toolMocks);
455492

456-
$this->assertEquals(Command::SUCCESS, $this->command->listAllTools());
493+
$this->assertEquals(Command::SUCCESS, $this->command->execute($this->inputMock, $this->outputMock));
457494
}
458495

459496
/**
460497
* Tests that when a tool class cannot be loaded, it is gracefully handled.
461498
*/
462499
public function test_list_all_tools_handles_tool_loading_exceptions_gracefully(): void
463500
{
501+
$this->inputMock
502+
->expects($this->once())
503+
->method('getOption')
504+
->with('list')
505+
->willReturn('list');
464506
$tools = [HelloWorldTool::class];
465507

466508
$this->containerMock
@@ -477,7 +519,7 @@ public function test_list_all_tools_handles_tool_loading_exceptions_gracefully()
477519
->method('warning')
478520
->with("Couldn't load tool class: " . HelloWorldTool::class);;
479521

480-
$this->assertEquals(Command::SUCCESS, $this->command->listAllTools());
522+
$this->assertEquals(Command::SUCCESS, $this->command->execute($this->inputMock, $this->outputMock));
481523
}
482524

483525
/**
@@ -713,4 +755,32 @@ public function test_ask_for_input_data_handle_alpha_for_integer(): void
713755

714756
$this->assertEquals([], $result);
715757
}
758+
759+
/**
760+
* Tests that askForInputData handles invalid JSON for object property.
761+
*/
762+
public function test_ask_for_input_data_handle_alpha_for_required_integer(): void
763+
{
764+
$schema = [
765+
'properties' => [
766+
'age' => [
767+
'type' => 'integer',
768+
'description' => 'How old are you?',
769+
],
770+
],
771+
'required' => ['age'],
772+
];
773+
$this->ioMock
774+
->method('ask')
775+
->with('Value', false)
776+
->willReturn('twenty');
777+
$this->ioMock
778+
->expects($this->once())
779+
->method('warning')
780+
->with('Required field skipped. Using 0.');
781+
782+
$result = $this->command->askForInputData($schema);
783+
784+
$this->assertEquals(['age' => 0], $result);
785+
}
716786
}

tests/KlpMcpServerBundleTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace KLP\KlpMcpServer\Tests;
4+
5+
use KLP\KlpMcpServer\DependencyInjection\CompilerPass\ToolsDefinitionCompilerPass;
6+
use KLP\KlpMcpServer\KlpMcpServerBundle;
7+
use PHPUnit\Framework\TestCase;
8+
use Symfony\Component\DependencyInjection\ContainerBuilder;
9+
10+
/**
11+
* This class tests the build method of the KlpMcpServerBundle class.
12+
*/
13+
class KlpMcpServerBundleTest extends TestCase
14+
{
15+
/**
16+
* Tests that the build method adds the ToolsDefinitionCompilerPass to the container.
17+
*/
18+
public function test_build_adds_compiler_pass()
19+
{
20+
$bundle = new KlpMcpServerBundle();
21+
$containerBuilder = $this->createMock(ContainerBuilder::class);
22+
23+
$containerBuilder
24+
->expects($this->once())
25+
->method('addCompilerPass')
26+
->with($this->isInstanceOf(ToolsDefinitionCompilerPass::class));
27+
28+
$bundle->build($containerBuilder);
29+
}
30+
}

0 commit comments

Comments
 (0)