Files
datahub/backend/test/Cases/Unit/Middleware/AuthMiddlewareApiKeyTest.php
T
2026-04-17 11:06:34 +08:00

221 lines
7.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace HyperfTest\Cases\Unit\Middleware;
use App\Middleware\AuthMiddleware;
use App\Model\ApiKey;
use App\Model\User;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Psr7\ServerRequest;
use Hyperf\HttpServer\Contract\ResponseInterface as HttpResponse;
use HyperfTest\TestCase;
use Mockery;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
use Qbhy\HyperfAuth\AuthManager;
use function Hyperf\Support\make;
/**
* AuthMiddleware 单元测试
*
* 验证 JWT 和 API Key 认证路径的 request attribute 设置行为
*
* @internal
* @coversNothing
*/
class AuthMiddlewareApiKeyTest extends TestCase
{
protected function createTestUser(array $overrides = []): User
{
$suffix = bin2hex(random_bytes(4));
return User::query()->create(array_merge([
'username' => 'auth_mw_' . $suffix,
'email' => 'auth_mw_' . $suffix . '@example.com',
'password' => 'Pass_' . $suffix,
'status' => 1,
'api_key_enabled' => true,
], $overrides));
}
protected function getAuthToken(User $user): string
{
$auth = make(AuthManager::class);
return $auth->guard('jwt')->login($user);
}
/**
* 构建中间件实例(使用 mock response 避免 ResponseContext 依赖)
*/
protected function buildMiddleware(): AuthMiddleware
{
$mock_response = Mockery::mock(HttpResponse::class);
$mock_response->shouldReceive('json')->andReturnUsing(function (array $data) {
$status = $data['code'] ?? 200;
return new Response($status, ['Content-Type' => 'application/json'], json_encode($data));
});
return new AuthMiddleware(
make(AuthManager::class),
$mock_response
);
}
/**
* 构建 mock handler,捕获传入的 request 到 Context
*/
protected function buildMockHandler(): RequestHandlerInterface
{
$handler = Mockery::mock(RequestHandlerInterface::class);
$handler->shouldReceive('handle')
->once()
->andReturnUsing(function (ServerRequestInterface $request): Response {
\Hyperf\Context\Context::set('test_captured_request', $request);
return new Response(200, [], '{"code":0}');
});
return $handler;
}
// ========== API Key 认证 attribute 测试 ==========
public function test_apikey_auth_sets_request_attribute(): void
{
$user = $this->createTestUser();
$result = ApiKey::generate($user->id, 'test_attr');
$plain_key = $result['plain_key'];
$middleware = $this->buildMiddleware();
$handler = $this->buildMockHandler();
$request = new ServerRequest('GET', '/test', ['X-API-Key' => $plain_key]);
$middleware->process($request, $handler);
$captured = \Hyperf\Context\Context::get('test_captured_request');
$this->assertNotNull($captured, 'handler 应被调用并传入 request');
$this->assertInstanceOf(User::class, $captured->getAttribute('auth_user'));
$this->assertSame($user->id, $captured->getAttribute('auth_user')->id);
$this->assertSame('api_key', $captured->getAttribute('auth_type'));
}
public function test_apikey_auth_does_not_generate_jwt(): void
{
$user = $this->createTestUser();
$result = ApiKey::generate($user->id, 'test_no_jwt');
$plain_key = $result['plain_key'];
$middleware = $this->buildMiddleware();
$handler = $this->buildMockHandler();
$request = new ServerRequest('GET', '/test', ['X-API-Key' => $plain_key]);
$middleware->process($request, $handler);
$captured = \Hyperf\Context\Context::get('test_captured_request');
// request 不应含有 Authorization header(不再注入临时 JWT
$this->assertSame('', $captured->getHeaderLine('Authorization'));
// auth_type 应为 api_key,而非 jwt
$this->assertSame('api_key', $captured->getAttribute('auth_type'));
}
/**
* JWT 认证设置 request attribute 测试
*
* 通过集成 HTTP 请求验证,因为 JWT guard 依赖 Swow RequestContext
* 无法直接用 GuzzleHttp\Psr7\ServerRequest 构造。
* /me 端点优先从 auth_user attribute 获取用户,验证认证成功即证明 attribute 工作正常。
*/
public function test_jwt_auth_sets_request_attribute(): void
{
$user = $this->createTestUser();
$response = $this->get('/api/v1/me', [], [
'Authorization' => 'Bearer ' . $this->getAuthToken($user),
]);
$response->assertStatus(200);
$response->assertJsonPath('code', 0);
$response->assertJsonPath('data.id', $user->id);
}
// ========== 错误场景测试 ==========
public function test_invalid_apikey_returns_401(): void
{
$middleware = $this->buildMiddleware();
$handler = Mockery::mock(RequestHandlerInterface::class);
$handler->shouldNotReceive('handle');
$request = new ServerRequest('GET', '/test', ['X-API-Key' => 'invalid_key_format']);
$response = $middleware->process($request, $handler);
$this->assertSame(401, $response->getStatusCode());
}
public function test_disabled_apikey_returns_403(): void
{
$user = $this->createTestUser();
$result = ApiKey::generate($user->id, 'test_disabled');
$plain_key = $result['plain_key'];
// 禁用 API Key
$result['api_key']->enabled = false;
$result['api_key']->save();
$middleware = $this->buildMiddleware();
$handler = Mockery::mock(RequestHandlerInterface::class);
$handler->shouldNotReceive('handle');
$request = new ServerRequest('GET', '/test', ['X-API-Key' => $plain_key]);
$response = $middleware->process($request, $handler);
$this->assertSame(403, $response->getStatusCode());
}
public function test_disabled_user_apikey_returns_403(): void
{
$user = $this->createTestUser();
$result = ApiKey::generate($user->id, 'test_disabled_user');
$plain_key = $result['plain_key'];
// 禁用用户
$user->status = 0;
$user->save();
$middleware = $this->buildMiddleware();
$handler = Mockery::mock(RequestHandlerInterface::class);
$handler->shouldNotReceive('handle');
$request = new ServerRequest('GET', '/test', ['X-API-Key' => $plain_key]);
$response = $middleware->process($request, $handler);
$this->assertSame(403, $response->getStatusCode());
}
public function test_user_api_key_not_enabled_returns_403(): void
{
$user = $this->createTestUser(['api_key_enabled' => false]);
$result = ApiKey::generate($user->id, 'test_not_enabled');
$plain_key = $result['plain_key'];
$middleware = $this->buildMiddleware();
$handler = Mockery::mock(RequestHandlerInterface::class);
$handler->shouldNotReceive('handle');
$request = new ServerRequest('GET', '/test', ['X-API-Key' => $plain_key]);
$response = $middleware->process($request, $handler);
$this->assertSame(403, $response->getStatusCode());
}
protected function tearDown(): void
{
Mockery::close();
\Hyperf\Context\Context::set('test_captured_request', null);
parent::tearDown();
}
}