167 lines
7.1 KiB
PHP
167 lines
7.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Admin;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use LIT\MCP\Admin\ConnectionRepository;
|
|
use LIT\MCP\Tests\Unit\TestCase;
|
|
use Mockery;
|
|
use WP_Application_Passwords;
|
|
|
|
final class ConnectionRepositoryTest extends TestCase
|
|
{
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
WP_Application_Passwords::$items = [
|
|
3 => [
|
|
'uuid-1' => ['uuid' => 'uuid-1', 'name' => 'Claude', 'app_id' => 'https://claude.ai/x', 'created' => 1790000000, 'last_used' => null],
|
|
'uuid-other' => ['uuid' => 'uuid-other', 'name' => 'Backup-Tool', 'app_id' => '', 'created' => 1, 'last_used' => null],
|
|
'uuid-foreign' => ['uuid' => 'uuid-foreign', 'name' => 'Fremd', 'app_id' => 'backup-tool', 'created' => 2, 'last_used' => 5],
|
|
],
|
|
4 => [
|
|
'uuid-4' => ['uuid' => 'uuid-4', 'name' => 'Claude', 'app_id' => 'https://claude.ai/x', 'created' => 3, 'last_used' => 7],
|
|
],
|
|
5 => [
|
|
'uuid-5' => ['uuid' => 'uuid-5', 'name' => 'Claude', 'app_id' => 'https://claude.ai/x', 'created' => 4, 'last_used' => null],
|
|
],
|
|
];
|
|
Functions\when('is_multisite')->justReturn(false);
|
|
Functions\when('current_user_can')->justReturn(true);
|
|
Functions\when('get_userdata')->alias(
|
|
static fn (int $id): object => (object) ['user_login' => 'user' . $id]
|
|
);
|
|
Functions\when('get_user_meta')->alias(
|
|
static fn (int $user, string $key): string => in_array(
|
|
$user . ':' . $key,
|
|
['3:mcp_refresh_jti_uuid-1', '3:mcp_refresh_jti_uuid-foreign', '4:mcp_refresh_jti_uuid-4', '5:mcp_refresh_jti_uuid-5'],
|
|
true
|
|
) ? 'jti' : ''
|
|
);
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
WP_Application_Passwords::$items = [];
|
|
parent::tearDown();
|
|
}
|
|
|
|
/** @param list<array{0: string, 1: string}> $rows user ID, meta key */
|
|
private function db(array $rows): \wpdb
|
|
{
|
|
$db = Mockery::mock(\wpdb::class)->makePartial();
|
|
$db->expects('get_results')
|
|
->with(Mockery::on(static fn (string $sql): bool => str_contains($sql, 'FROM wp_usermeta')
|
|
&& str_contains($sql, "LIKE 'mcp\\_refresh\\_jti\\_%'")))
|
|
->andReturn(array_map(
|
|
static fn (array $row): object => (object) ['user_id' => $row[0], 'meta_key' => $row[1]],
|
|
$rows
|
|
));
|
|
|
|
return $db;
|
|
}
|
|
|
|
public function testListsOnlyMcpOauthConnections(): void
|
|
{
|
|
$db = $this->db([
|
|
['3', 'mcp_refresh_jti_uuid-1'],
|
|
['3', 'mcp_refresh_jti_uuid-gone'],
|
|
['3', 'mcp_refresh_jti_uuid-foreign'],
|
|
]);
|
|
|
|
$connections = (new ConnectionRepository($db))->all();
|
|
|
|
self::assertCount(1, $connections);
|
|
self::assertSame(3, $connections[0]['user_id']);
|
|
self::assertSame('user3', $connections[0]['user_login']);
|
|
self::assertSame('Claude', $connections[0]['name']);
|
|
self::assertSame('uuid-1', $connections[0]['uuid']);
|
|
self::assertSame('https://claude.ai/x', $connections[0]['app_id']);
|
|
self::assertSame(1790000000, $connections[0]['created']);
|
|
self::assertNull($connections[0]['last_used']);
|
|
}
|
|
|
|
public function testMultisiteListsOnlyMembersAndHidesSuperAdminsFromOtherAdmins(): void
|
|
{
|
|
Functions\when('is_multisite')->justReturn(true);
|
|
Functions\when('get_current_blog_id')->justReturn(2);
|
|
Functions\when('is_user_member_of_blog')->alias(static fn (int $user, int $blog): bool => $blog === 2 && $user !== 4);
|
|
Functions\when('is_super_admin')->alias(static fn (?int $user = null): bool => $user === 5);
|
|
$db = $this->db([
|
|
['3', 'mcp_refresh_jti_uuid-1'],
|
|
['4', 'mcp_refresh_jti_uuid-4'],
|
|
['5', 'mcp_refresh_jti_uuid-5'],
|
|
]);
|
|
|
|
$connections = (new ConnectionRepository($db))->all();
|
|
|
|
self::assertSame(['uuid-1'], array_column($connections, 'uuid'));
|
|
}
|
|
|
|
public function testMultisiteSuperAdminSeesOtherSuperAdminsOfTheSite(): void
|
|
{
|
|
Functions\when('is_multisite')->justReturn(true);
|
|
Functions\when('get_current_blog_id')->justReturn(2);
|
|
Functions\when('is_user_member_of_blog')->alias(static fn (int $user, int $blog): bool => $user !== 4);
|
|
Functions\when('is_super_admin')->alias(static fn (?int $user = null): bool => $user === null || $user === 5);
|
|
$db = $this->db([
|
|
['3', 'mcp_refresh_jti_uuid-1'],
|
|
['4', 'mcp_refresh_jti_uuid-4'],
|
|
['5', 'mcp_refresh_jti_uuid-5'],
|
|
]);
|
|
|
|
$connections = (new ConnectionRepository($db))->all();
|
|
|
|
self::assertSame(['uuid-1', 'uuid-5'], array_column($connections, 'uuid'));
|
|
}
|
|
|
|
public function testRevokesOnlyMcpOauthConnections(): void
|
|
{
|
|
$repository = new ConnectionRepository(Mockery::mock(\wpdb::class)->makePartial());
|
|
|
|
self::assertFalse($repository->revoke(3, 'uuid-other'));
|
|
self::assertArrayHasKey('uuid-other', WP_Application_Passwords::$items[3]);
|
|
self::assertFalse($repository->revoke(3, 'uuid-foreign'), 'marker meta alone is not enough');
|
|
self::assertArrayHasKey('uuid-foreign', WP_Application_Passwords::$items[3]);
|
|
self::assertFalse($repository->revoke(3, 'uuid-gone'));
|
|
self::assertFalse($repository->revoke(0, 'uuid-1'));
|
|
self::assertFalse($repository->revoke(3, ''));
|
|
self::assertTrue($repository->revoke(3, 'uuid-1'));
|
|
self::assertArrayNotHasKey('uuid-1', WP_Application_Passwords::$items[3]);
|
|
}
|
|
|
|
public function testMultisiteRevokeRefusesNonMembersAndSuperAdmins(): void
|
|
{
|
|
Functions\when('is_multisite')->justReturn(true);
|
|
Functions\when('get_current_blog_id')->justReturn(2);
|
|
Functions\when('is_user_member_of_blog')->alias(static fn (int $user, int $blog): bool => $user !== 4);
|
|
Functions\when('is_super_admin')->alias(static fn (?int $user = null): bool => $user === 5);
|
|
$repository = new ConnectionRepository(Mockery::mock(\wpdb::class)->makePartial());
|
|
|
|
self::assertFalse($repository->revoke(4, 'uuid-4'));
|
|
self::assertFalse($repository->revoke(5, 'uuid-5'));
|
|
self::assertArrayHasKey('uuid-4', WP_Application_Passwords::$items[4]);
|
|
self::assertArrayHasKey('uuid-5', WP_Application_Passwords::$items[5]);
|
|
self::assertTrue($repository->revoke(3, 'uuid-1'));
|
|
}
|
|
|
|
public function testListsAndRevokesOnlyConnectionsOfUsersTheAdminMayEdit(): void
|
|
{
|
|
Functions\when('current_user_can')->alias(
|
|
static fn (string $cap, ...$args): bool => ! ($cap === 'edit_user' && $args === [4])
|
|
);
|
|
$db = $this->db([
|
|
['3', 'mcp_refresh_jti_uuid-1'],
|
|
['4', 'mcp_refresh_jti_uuid-4'],
|
|
]);
|
|
$repository = new ConnectionRepository($db);
|
|
|
|
self::assertSame(['uuid-1'], array_column($repository->all(), 'uuid'));
|
|
self::assertFalse($repository->revoke(4, 'uuid-4'));
|
|
self::assertArrayHasKey('uuid-4', WP_Application_Passwords::$items[4]);
|
|
self::assertTrue($repository->revoke(3, 'uuid-1'));
|
|
}
|
|
}
|