diff --git a/src/Client/Configuration.php b/src/Client/Configuration.php index 8c7e909b..98ad2d83 100644 --- a/src/Client/Configuration.php +++ b/src/Client/Configuration.php @@ -11,6 +11,7 @@ namespace Mcp\Client; +use Mcp\Exception\InvalidArgumentException; use Mcp\Schema\ClientCapabilities; use Mcp\Schema\Enum\ProtocolVersion; use Mcp\Schema\Implementation; @@ -30,5 +31,12 @@ public function __construct( public readonly int $requestTimeout = 120, public readonly int $maxRetries = 3, ) { + if ($initTimeout < 1) { + throw new InvalidArgumentException(\sprintf('The initialization timeout must be a positive number of seconds, got %d.', $initTimeout)); + } + + if ($requestTimeout < 1) { + throw new InvalidArgumentException(\sprintf('The request timeout must be a positive number of seconds, got %d.', $requestTimeout)); + } } } diff --git a/tests/Unit/Client/ConfigurationTest.php b/tests/Unit/Client/ConfigurationTest.php new file mode 100644 index 00000000..ae50a499 --- /dev/null +++ b/tests/Unit/Client/ConfigurationTest.php @@ -0,0 +1,92 @@ +expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('The initialization timeout must be a positive number of seconds, got %d.', $seconds)); + + $this->createConfiguration(initTimeout: $seconds); + } + + #[TestDox('a non-positive request timeout of $seconds seconds is rejected')] + #[DataProvider('provideNonPositiveTimeouts')] + public function testNonPositiveRequestTimeoutIsRejected(int $seconds): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(\sprintf('The request timeout must be a positive number of seconds, got %d.', $seconds)); + + $this->createConfiguration(requestTimeout: $seconds); + } + + /** + * @return iterable + */ + public static function provideNonPositiveTimeouts(): iterable + { + yield 'zero' => [0]; + yield 'negative' => [-1]; + } + + #[TestDox('the builder rejects a non-positive initialization timeout')] + public function testBuilderRejectsNonPositiveInitTimeout(): void + { + $builder = Client::builder()->setInitTimeout(0); + + $this->expectException(InvalidArgumentException::class); + + $builder->build(); + } + + #[TestDox('the builder rejects a non-positive request timeout')] + public function testBuilderRejectsNonPositiveRequestTimeout(): void + { + $builder = Client::builder()->setRequestTimeout(-5); + + $this->expectException(InvalidArgumentException::class); + + $builder->build(); + } + + #[TestDox('positive timeouts are accepted')] + public function testPositiveTimeoutsAreAccepted(): void + { + $config = $this->createConfiguration(initTimeout: 1, requestTimeout: 1); + + $this->assertSame(1, $config->initTimeout); + $this->assertSame(1, $config->requestTimeout); + } + + private function createConfiguration(int $initTimeout = 30, int $requestTimeout = 120): Configuration + { + return new Configuration( + clientInfo: new Implementation('test-client', '1.0.0'), + capabilities: new ClientCapabilities(), + initTimeout: $initTimeout, + requestTimeout: $requestTimeout, + ); + } +}