php-mcp/src/Mcp/Tests/Unit/ToolVersionServiceTest.php
Snider 6f309979de refactor: move MCP module from Core\Mod\Mcp to Core\Mcp namespace
Relocates the MCP module to a top-level namespace as part of the
monorepo separation, removing the intermediate Mod directory layer.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 16:26:14 +00:00

441 lines
16 KiB
PHP

<?php
declare(strict_types=1);
namespace Core\Mcp\Tests\Unit;
use Core\Mcp\Services\ToolVersionService;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Tests\TestCase;
class ToolVersionServiceTest extends TestCase
{
use RefreshDatabase;
protected ToolVersionService $service;
protected function setUp(): void
{
parent::setUp();
$this->service = new ToolVersionService;
}
public function test_can_register_new_version(): void
{
$version = $this->service->registerVersion(
serverId: 'test-server',
toolName: 'test_tool',
version: '1.0.0',
inputSchema: ['type' => 'object', 'properties' => ['query' => ['type' => 'string']]],
description: 'A test tool',
options: ['mark_latest' => true]
);
$this->assertSame('test-server', $version->server_id);
$this->assertSame('test_tool', $version->tool_name);
$this->assertSame('1.0.0', $version->version);
$this->assertTrue($version->is_latest);
}
public function test_first_version_is_automatically_latest(): void
{
$version = $this->service->registerVersion(
serverId: 'test-server',
toolName: 'test_tool',
version: '1.0.0',
);
$this->assertTrue($version->is_latest);
}
public function test_can_get_tool_at_specific_version(): void
{
$this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$this->service->registerVersion('test-server', 'test_tool', '2.0.0');
$v1 = $this->service->getToolAtVersion('test-server', 'test_tool', '1.0.0');
$v2 = $this->service->getToolAtVersion('test-server', 'test_tool', '2.0.0');
$this->assertSame('1.0.0', $v1->version);
$this->assertSame('2.0.0', $v2->version);
}
public function test_get_latest_version(): void
{
$this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$v2 = $this->service->registerVersion('test-server', 'test_tool', '2.0.0', options: ['mark_latest' => true]);
$latest = $this->service->getLatestVersion('test-server', 'test_tool');
$this->assertSame('2.0.0', $latest->version);
$this->assertTrue($latest->is_latest);
}
public function test_resolve_version_returns_latest_when_no_version_specified(): void
{
$this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$this->service->registerVersion('test-server', 'test_tool', '2.0.0', options: ['mark_latest' => true]);
$result = $this->service->resolveVersion('test-server', 'test_tool', null);
$this->assertNotNull($result['version']);
$this->assertSame('2.0.0', $result['version']->version);
$this->assertNull($result['warning']);
$this->assertNull($result['error']);
}
public function test_resolve_version_returns_specific_version(): void
{
$this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$this->service->registerVersion('test-server', 'test_tool', '2.0.0', options: ['mark_latest' => true]);
$result = $this->service->resolveVersion('test-server', 'test_tool', '1.0.0');
$this->assertNotNull($result['version']);
$this->assertSame('1.0.0', $result['version']->version);
}
public function test_resolve_version_returns_error_for_nonexistent_version(): void
{
$this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$result = $this->service->resolveVersion('test-server', 'test_tool', '9.9.9');
$this->assertNull($result['version']);
$this->assertNotNull($result['error']);
$this->assertSame('VERSION_NOT_FOUND', $result['error']['code']);
}
public function test_resolve_deprecated_version_returns_warning(): void
{
$version = $this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$version->deprecate(Carbon::now()->addDays(30));
$this->service->registerVersion('test-server', 'test_tool', '2.0.0', options: ['mark_latest' => true]);
$result = $this->service->resolveVersion('test-server', 'test_tool', '1.0.0');
$this->assertNotNull($result['version']);
$this->assertNotNull($result['warning']);
$this->assertSame('TOOL_VERSION_DEPRECATED', $result['warning']['code']);
}
public function test_resolve_sunset_version_returns_error(): void
{
$version = $this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$version->deprecated_at = Carbon::now()->subDays(60);
$version->sunset_at = Carbon::now()->subDays(30);
$version->save();
$this->service->registerVersion('test-server', 'test_tool', '2.0.0', options: ['mark_latest' => true]);
$result = $this->service->resolveVersion('test-server', 'test_tool', '1.0.0');
$this->assertNull($result['version']);
$this->assertNotNull($result['error']);
$this->assertSame('TOOL_VERSION_SUNSET', $result['error']['code']);
}
public function test_is_deprecated(): void
{
$version = $this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$version->deprecate();
$this->assertTrue($this->service->isDeprecated('test-server', 'test_tool', '1.0.0'));
}
public function test_is_sunset(): void
{
$version = $this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$version->deprecated_at = Carbon::now()->subDays(60);
$version->sunset_at = Carbon::now()->subDays(30);
$version->save();
$this->assertTrue($this->service->isSunset('test-server', 'test_tool', '1.0.0'));
}
public function test_compare_versions(): void
{
$this->assertSame(-1, $this->service->compareVersions('1.0.0', '2.0.0'));
$this->assertSame(0, $this->service->compareVersions('1.0.0', '1.0.0'));
$this->assertSame(1, $this->service->compareVersions('2.0.0', '1.0.0'));
$this->assertSame(-1, $this->service->compareVersions('1.0.0', '1.0.1'));
$this->assertSame(-1, $this->service->compareVersions('1.0.0', '1.1.0'));
}
public function test_get_version_history(): void
{
$this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$this->service->registerVersion('test-server', 'test_tool', '1.1.0');
$this->service->registerVersion('test-server', 'test_tool', '2.0.0');
$history = $this->service->getVersionHistory('test-server', 'test_tool');
$this->assertCount(3, $history);
// Should be ordered by version desc
$this->assertSame('2.0.0', $history[0]->version);
$this->assertSame('1.1.0', $history[1]->version);
$this->assertSame('1.0.0', $history[2]->version);
}
public function test_migrate_tool_call(): void
{
$this->service->registerVersion(
serverId: 'test-server',
toolName: 'test_tool',
version: '1.0.0',
inputSchema: [
'type' => 'object',
'properties' => ['query' => ['type' => 'string']],
'required' => ['query'],
]
);
$this->service->registerVersion(
serverId: 'test-server',
toolName: 'test_tool',
version: '2.0.0',
inputSchema: [
'type' => 'object',
'properties' => [
'query' => ['type' => 'string'],
'limit' => ['type' => 'integer', 'default' => 10],
],
'required' => ['query', 'limit'],
]
);
$result = $this->service->migrateToolCall(
serverId: 'test-server',
toolName: 'test_tool',
fromVersion: '1.0.0',
toVersion: '2.0.0',
arguments: ['query' => 'SELECT * FROM users']
);
$this->assertTrue($result['success']);
$this->assertSame('SELECT * FROM users', $result['arguments']['query']);
$this->assertSame(10, $result['arguments']['limit']); // Default applied
}
public function test_deprecate_version(): void
{
$this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$sunsetDate = Carbon::now()->addDays(30);
$deprecatedVersion = $this->service->deprecateVersion(
'test-server',
'test_tool',
'1.0.0',
$sunsetDate
);
$this->assertNotNull($deprecatedVersion->deprecated_at);
$this->assertSame($sunsetDate->toDateString(), $deprecatedVersion->sunset_at->toDateString());
}
public function test_get_tools_with_versions(): void
{
$this->service->registerVersion('test-server', 'tool_a', '1.0.0');
$this->service->registerVersion('test-server', 'tool_a', '2.0.0', options: ['mark_latest' => true]);
$this->service->registerVersion('test-server', 'tool_b', '1.0.0');
$tools = $this->service->getToolsWithVersions('test-server');
$this->assertCount(2, $tools);
$this->assertArrayHasKey('tool_a', $tools);
$this->assertArrayHasKey('tool_b', $tools);
$this->assertSame(2, $tools['tool_a']['version_count']);
$this->assertSame(1, $tools['tool_b']['version_count']);
}
public function test_get_servers_with_versions(): void
{
$this->service->registerVersion('server-a', 'tool', '1.0.0');
$this->service->registerVersion('server-b', 'tool', '1.0.0');
$servers = $this->service->getServersWithVersions();
$this->assertCount(2, $servers);
$this->assertContains('server-a', $servers);
$this->assertContains('server-b', $servers);
}
public function test_sync_from_server_config(): void
{
$config = [
'id' => 'test-server',
'tools' => [
[
'name' => 'tool_a',
'description' => 'Tool A',
'inputSchema' => ['type' => 'object'],
],
[
'name' => 'tool_b',
'purpose' => 'Tool B purpose',
],
],
];
$registered = $this->service->syncFromServerConfig($config, '1.0.0');
$this->assertSame(2, $registered);
$toolA = $this->service->getToolAtVersion('test-server', 'tool_a', '1.0.0');
$toolB = $this->service->getToolAtVersion('test-server', 'tool_b', '1.0.0');
$this->assertNotNull($toolA);
$this->assertNotNull($toolB);
$this->assertSame('Tool A', $toolA->description);
$this->assertSame('Tool B purpose', $toolB->description);
}
public function test_get_stats(): void
{
$this->service->registerVersion('server-a', 'tool_a', '1.0.0');
$this->service->registerVersion('server-a', 'tool_a', '2.0.0');
$this->service->registerVersion('server-b', 'tool_b', '1.0.0');
$stats = $this->service->getStats();
$this->assertSame(3, $stats['total_versions']);
$this->assertSame(2, $stats['total_tools']);
$this->assertSame(2, $stats['servers']);
}
public function test_invalid_semver_throws_exception(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid semver version');
$this->service->registerVersion('test-server', 'test_tool', 'invalid');
}
public function test_valid_semver_formats(): void
{
// Basic versions
$v1 = $this->service->registerVersion('test-server', 'tool', '1.0.0');
$this->assertSame('1.0.0', $v1->version);
// Prerelease
$v2 = $this->service->registerVersion('test-server', 'tool', '2.0.0-beta');
$this->assertSame('2.0.0-beta', $v2->version);
// Prerelease with dots
$v3 = $this->service->registerVersion('test-server', 'tool', '2.0.0-alpha.1');
$this->assertSame('2.0.0-alpha.1', $v3->version);
// Build metadata
$v4 = $this->service->registerVersion('test-server', 'tool', '2.0.0+build.123');
$this->assertSame('2.0.0+build.123', $v4->version);
}
public function test_updating_existing_version(): void
{
$original = $this->service->registerVersion(
serverId: 'test-server',
toolName: 'test_tool',
version: '1.0.0',
description: 'Original description'
);
$updated = $this->service->registerVersion(
serverId: 'test-server',
toolName: 'test_tool',
version: '1.0.0',
description: 'Updated description'
);
$this->assertSame($original->id, $updated->id);
$this->assertSame('Updated description', $updated->description);
}
public function test_model_compare_schema_with(): void
{
$v1 = $this->service->registerVersion(
serverId: 'test-server',
toolName: 'test_tool',
version: '1.0.0',
inputSchema: [
'type' => 'object',
'properties' => [
'query' => ['type' => 'string'],
'format' => ['type' => 'string'],
],
]
);
$v2 = $this->service->registerVersion(
serverId: 'test-server',
toolName: 'test_tool',
version: '2.0.0',
inputSchema: [
'type' => 'object',
'properties' => [
'query' => ['type' => 'string', 'maxLength' => 1000], // Changed
'limit' => ['type' => 'integer'], // Added
],
]
);
$diff = $v1->compareSchemaWith($v2);
$this->assertContains('limit', $diff['added']);
$this->assertContains('format', $diff['removed']);
$this->assertArrayHasKey('query', $diff['changed']);
}
public function test_model_mark_as_latest(): void
{
$v1 = $this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$v2 = $this->service->registerVersion('test-server', 'test_tool', '2.0.0');
$v2->markAsLatest();
$this->assertFalse($v1->fresh()->is_latest);
$this->assertTrue($v2->fresh()->is_latest);
}
public function test_model_status_attribute(): void
{
$version = $this->service->registerVersion('test-server', 'test_tool', '1.0.0');
$this->assertSame('latest', $version->status);
$version->is_latest = false;
$version->save();
$this->assertSame('active', $version->fresh()->status);
$version->deprecated_at = Carbon::now()->subDay();
$version->save();
$this->assertSame('deprecated', $version->fresh()->status);
$version->sunset_at = Carbon::now()->subDay();
$version->save();
$this->assertSame('sunset', $version->fresh()->status);
}
public function test_model_to_api_array(): void
{
$version = $this->service->registerVersion(
serverId: 'test-server',
toolName: 'test_tool',
version: '1.0.0',
inputSchema: ['type' => 'object'],
description: 'Test tool',
options: ['changelog' => 'Initial release']
);
$array = $version->toApiArray();
$this->assertSame('test-server', $array['server_id']);
$this->assertSame('test_tool', $array['tool_name']);
$this->assertSame('1.0.0', $array['version']);
$this->assertTrue($array['is_latest']);
$this->assertSame('latest', $array['status']);
$this->assertSame('Test tool', $array['description']);
$this->assertSame('Initial release', $array['changelog']);
}
}