105 lines
2.8 KiB
PHP
105 lines
2.8 KiB
PHP
<?php
|
|
|
|
use App\Features\Mcp;
|
|
use App\Models\User;
|
|
use Laravel\Pennant\Feature;
|
|
|
|
use function Pest\Laravel\actingAs;
|
|
use function Pest\Laravel\get;
|
|
|
|
/**
|
|
* A user with the MCP rollout feature flag enabled.
|
|
*/
|
|
function mcpUser(): User
|
|
{
|
|
$user = User::factory()->create();
|
|
Feature::for($user)->activate(Mcp::class);
|
|
|
|
return $user;
|
|
}
|
|
|
|
it('requires authentication to view the MCP page', function () {
|
|
get(route('mcp.index'))->assertRedirect();
|
|
});
|
|
|
|
it('hides the MCP settings page when the feature flag is off', function () {
|
|
actingAs(User::factory()->create())
|
|
->get(route('mcp.index'))
|
|
->assertNotFound();
|
|
});
|
|
|
|
it('renders the MCP settings page', function () {
|
|
actingAs(mcpUser())
|
|
->get(route('mcp.index'))
|
|
->assertOk();
|
|
});
|
|
|
|
it('creates a read-only token and flashes the secret once', function () {
|
|
$user = mcpUser();
|
|
|
|
actingAs($user)
|
|
->post(route('mcp.tokens.store'), ['name' => 'Claude Desktop'])
|
|
->assertRedirect(route('mcp.index'))
|
|
->assertSessionHas('mcp_token');
|
|
|
|
$token = $user->tokens()->first();
|
|
|
|
expect($token->name)->toBe('Claude Desktop');
|
|
expect($token->abilities)->toBe(['mcp:read']);
|
|
});
|
|
|
|
it('requires a token name', function () {
|
|
actingAs(mcpUser())
|
|
->post(route('mcp.tokens.store'), ['name' => ''])
|
|
->assertSessionHasErrors('name');
|
|
});
|
|
|
|
it('lets a free account create a token (gating happens at request time)', function () {
|
|
config(['subscriptions.enabled' => true]);
|
|
$user = mcpUser();
|
|
|
|
actingAs($user)
|
|
->post(route('mcp.tokens.store'), ['name' => 'Free'])
|
|
->assertSessionHas('mcp_token');
|
|
|
|
expect($user->tokens()->count())->toBe(1);
|
|
});
|
|
|
|
it('revokes a token the user owns', function () {
|
|
$user = mcpUser();
|
|
$token = $user->createToken('X', ['mcp:read'])->accessToken;
|
|
|
|
actingAs($user)
|
|
->delete(route('mcp.tokens.destroy', $token->id))
|
|
->assertRedirect(route('mcp.index'));
|
|
|
|
expect($user->tokens()->count())->toBe(0);
|
|
});
|
|
|
|
it('cannot revoke another user\'s token', function () {
|
|
$user = mcpUser();
|
|
$other = User::factory()->create();
|
|
$token = $other->createToken('X', ['mcp:read'])->accessToken;
|
|
|
|
actingAs($user)
|
|
->delete(route('mcp.tokens.destroy', $token->id))
|
|
->assertForbidden();
|
|
|
|
expect($other->tokens()->count())->toBe(1);
|
|
});
|
|
|
|
it('rotates a token, replacing the secret but keeping the scope', function () {
|
|
$user = mcpUser();
|
|
$token = $user->createToken('X', ['mcp:read'])->accessToken;
|
|
|
|
actingAs($user)
|
|
->post(route('mcp.tokens.rotate', $token->id))
|
|
->assertSessionHas('mcp_token');
|
|
|
|
$tokens = $user->tokens()->get();
|
|
|
|
expect($tokens)->toHaveCount(1);
|
|
expect($tokens->first()->id)->not->toBe($token->id);
|
|
expect($tokens->first()->abilities)->toBe(['mcp:read']);
|
|
});
|