update test
This commit is contained in:
@@ -0,0 +1,461 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace HyperfTest\Cases\Integration\Auth;
|
||||||
|
|
||||||
|
use App\Model\User;
|
||||||
|
use HyperfTest\TestCase;
|
||||||
|
use Qbhy\HyperfAuth\AuthManager;
|
||||||
|
|
||||||
|
use function Hyperf\Support\make;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class AuthControllerTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 获取认证用的 JWT Token
|
||||||
|
*/
|
||||||
|
protected function getAuthToken(?User $user = null): string
|
||||||
|
{
|
||||||
|
if (!$user) {
|
||||||
|
$user = $this->fetchUser(static function ($query): void {
|
||||||
|
$query->where('status', 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!$user) {
|
||||||
|
$this->markTestSkipped('没有可用的活跃用户,无法测试');
|
||||||
|
}
|
||||||
|
|
||||||
|
$auth = make(AuthManager::class);
|
||||||
|
return $auth->guard('jwt')->login($user);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function authHeaders(?User $user = null): array
|
||||||
|
{
|
||||||
|
return ['Authorization' => 'Bearer ' . $this->getAuthToken($user)];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function fetchUser(?callable $callback = null): ?User
|
||||||
|
{
|
||||||
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||||||
|
$query = User::query();
|
||||||
|
if ($callback !== null) {
|
||||||
|
$callback($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = null;
|
||||||
|
|
||||||
|
\Swoole\Coroutine\run(static function () use ($callback, &$user): void {
|
||||||
|
$query = User::query();
|
||||||
|
if ($callback !== null) {
|
||||||
|
$callback($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $query->first();
|
||||||
|
});
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createTestUser(array $overrides = []): User
|
||||||
|
{
|
||||||
|
$suffix = bin2hex(random_bytes(4));
|
||||||
|
|
||||||
|
return User::query()->create(array_merge([
|
||||||
|
'username' => 'auth_test_' . $suffix,
|
||||||
|
'password' => 'Pass_' . $suffix,
|
||||||
|
'email' => 'auth_test_' . $suffix . '@example.com',
|
||||||
|
'status' => 1,
|
||||||
|
], $overrides));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 注册接口参数校验 ==========
|
||||||
|
|
||||||
|
public function test_register_success(): void
|
||||||
|
{
|
||||||
|
$suffix = bin2hex(random_bytes(4));
|
||||||
|
|
||||||
|
$response = $this->post('/api/v1/register', [
|
||||||
|
'username' => 'reg_' . $suffix,
|
||||||
|
'password' => 'Pass_' . $suffix,
|
||||||
|
'email' => 'reg_' . $suffix . '@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('code', 0);
|
||||||
|
$response->assertJsonPath('data.username', 'reg_' . $suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_register_missing_username_returns_400(): void
|
||||||
|
{
|
||||||
|
$response = $this->post('/api/v1/register', [
|
||||||
|
'password' => 'Pass_1234',
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_register_short_username_returns_400(): void
|
||||||
|
{
|
||||||
|
$response = $this->post('/api/v1/register', [
|
||||||
|
'username' => 'ab',
|
||||||
|
'password' => 'Pass_1234',
|
||||||
|
'email' => 'test_short@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_register_missing_password_returns_400(): void
|
||||||
|
{
|
||||||
|
$suffix = bin2hex(random_bytes(4));
|
||||||
|
|
||||||
|
$response = $this->post('/api/v1/register', [
|
||||||
|
'username' => 'reg_' . $suffix,
|
||||||
|
'email' => 'reg_' . $suffix . '@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_register_short_password_returns_400(): void
|
||||||
|
{
|
||||||
|
$suffix = bin2hex(random_bytes(4));
|
||||||
|
|
||||||
|
$response = $this->post('/api/v1/register', [
|
||||||
|
'username' => 'reg_' . $suffix,
|
||||||
|
'password' => '12345',
|
||||||
|
'email' => 'reg_' . $suffix . '@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_register_invalid_email_returns_400(): void
|
||||||
|
{
|
||||||
|
$suffix = bin2hex(random_bytes(4));
|
||||||
|
|
||||||
|
$response = $this->post('/api/v1/register', [
|
||||||
|
'username' => 'reg_' . $suffix,
|
||||||
|
'password' => 'Pass_1234',
|
||||||
|
'email' => 'not-an-email',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_register_duplicate_username_returns_400(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
|
||||||
|
$response = $this->post('/api/v1/register', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'password' => 'Pass_1234',
|
||||||
|
'email' => 'dup_' . bin2hex(random_bytes(4)) . '@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 登录接口参数校验 ==========
|
||||||
|
|
||||||
|
public function test_login_success(): void
|
||||||
|
{
|
||||||
|
$password = 'Login_test_pass';
|
||||||
|
$user = $this->createTestUser(['password' => $password]);
|
||||||
|
|
||||||
|
$response = $this->post('/api/v1/login', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'password' => $password,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('code', 0);
|
||||||
|
$response->assertJsonStructure([
|
||||||
|
'data' => ['access_token', 'refresh_token', 'token_type', 'expires_in', 'user'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_login_missing_username_returns_400(): void
|
||||||
|
{
|
||||||
|
$response = $this->post('/api/v1/login', [
|
||||||
|
'password' => 'Pass_1234',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_login_missing_password_returns_400(): void
|
||||||
|
{
|
||||||
|
$response = $this->post('/api/v1/login', [
|
||||||
|
'username' => 'some_user',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_login_wrong_password_returns_401(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
|
||||||
|
$response = $this->post('/api/v1/login', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'password' => 'wrong_password_here',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(401);
|
||||||
|
$response->assertJsonPath('code', 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_login_disabled_user_returns_403(): void
|
||||||
|
{
|
||||||
|
$password = 'Disabled_pass';
|
||||||
|
$user = $this->createTestUser(['password' => $password, 'status' => 0]);
|
||||||
|
|
||||||
|
$response = $this->post('/api/v1/login', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'password' => $password,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(403);
|
||||||
|
$response->assertJsonPath('code', 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 密码修改接口 ==========
|
||||||
|
|
||||||
|
public function test_change_password_success(): void
|
||||||
|
{
|
||||||
|
$old_password = 'OldPass_1234';
|
||||||
|
$new_password = 'NewPass_5678';
|
||||||
|
$user = $this->createTestUser(['password' => $old_password]);
|
||||||
|
|
||||||
|
$response = $this->put('/api/v1/me/password', [
|
||||||
|
'old_password' => $old_password,
|
||||||
|
'new_password' => $new_password,
|
||||||
|
], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('code', 0);
|
||||||
|
|
||||||
|
// 验证 refresh_token 已被清除
|
||||||
|
$user->refresh();
|
||||||
|
$this->assertNull($user->refresh_token);
|
||||||
|
$this->assertNull($user->refresh_token_expires_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_change_password_clears_refresh_token(): void
|
||||||
|
{
|
||||||
|
$old_password = 'OldPass_clear';
|
||||||
|
$user = $this->createTestUser(['password' => $old_password]);
|
||||||
|
|
||||||
|
// 先登录获取 refresh_token
|
||||||
|
$this->post('/api/v1/login', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'password' => $old_password,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
$this->assertNotNull($user->refresh_token);
|
||||||
|
|
||||||
|
// 修改密码
|
||||||
|
$response = $this->put('/api/v1/me/password', [
|
||||||
|
'old_password' => $old_password,
|
||||||
|
'new_password' => 'NewPass_clear',
|
||||||
|
], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
$this->assertNull($user->refresh_token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_change_password_wrong_old_password_returns_400(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser(['password' => 'CorrectOld_1']);
|
||||||
|
|
||||||
|
$response = $this->put('/api/v1/me/password', [
|
||||||
|
'old_password' => 'WrongOld_pass',
|
||||||
|
'new_password' => 'NewPass_5678',
|
||||||
|
], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_change_password_short_new_password_returns_400(): void
|
||||||
|
{
|
||||||
|
$old_password = 'OldPass_short';
|
||||||
|
$user = $this->createTestUser(['password' => $old_password]);
|
||||||
|
|
||||||
|
$response = $this->put('/api/v1/me/password', [
|
||||||
|
'old_password' => $old_password,
|
||||||
|
'new_password' => '12345',
|
||||||
|
], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_change_password_missing_old_password_returns_400(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
|
||||||
|
$response = $this->put('/api/v1/me/password', [
|
||||||
|
'new_password' => 'NewPass_5678',
|
||||||
|
], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_change_password_without_token_returns_401(): void
|
||||||
|
{
|
||||||
|
$response = $this->put('/api/v1/me/password', [
|
||||||
|
'old_password' => 'old',
|
||||||
|
'new_password' => 'new_pass',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 个人信息更新接口 ==========
|
||||||
|
|
||||||
|
public function test_update_profile_email(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$new_email = 'profile_' . bin2hex(random_bytes(4)) . '@example.com';
|
||||||
|
|
||||||
|
$response = $this->put('/api/v1/me/profile', [
|
||||||
|
'email' => $new_email,
|
||||||
|
], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('code', 0);
|
||||||
|
$response->assertJsonPath('data.email', $new_email);
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
$this->assertSame($new_email, $user->email);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_profile_ext(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$ext = ['nickname' => 'TestNick', 'theme' => 'dark'];
|
||||||
|
|
||||||
|
$response = $this->put('/api/v1/me/profile', [
|
||||||
|
'ext' => $ext,
|
||||||
|
], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('code', 0);
|
||||||
|
$response->assertJsonPath('data.ext.nickname', 'TestNick');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_profile_invalid_email_returns_400(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
|
||||||
|
$response = $this->put('/api/v1/me/profile', [
|
||||||
|
'email' => 'not-an-email',
|
||||||
|
], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_profile_duplicate_email_returns_400(): void
|
||||||
|
{
|
||||||
|
$existing = $this->createTestUser();
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
|
||||||
|
$response = $this->put('/api/v1/me/profile', [
|
||||||
|
'email' => $existing->email,
|
||||||
|
], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_profile_no_fields_returns_400(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
|
||||||
|
$response = $this->put('/api/v1/me/profile', [], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(400);
|
||||||
|
$response->assertJsonPath('code', 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_update_profile_without_token_returns_401(): void
|
||||||
|
{
|
||||||
|
$response = $this->put('/api/v1/me/profile', [
|
||||||
|
'email' => 'test@example.com',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 现有接口测试 ==========
|
||||||
|
|
||||||
|
public function test_me_returns_user_info(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
|
||||||
|
$response = $this->get('/api/v1/me', [], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('code', 0);
|
||||||
|
$response->assertJsonPath('data.id', $user->id);
|
||||||
|
$response->assertJsonPath('data.username', $user->username);
|
||||||
|
$response->assertJsonStructure([
|
||||||
|
'data' => ['id', 'username', 'email', 'status', 'ext', 'created_at'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_me_without_token_returns_401(): void
|
||||||
|
{
|
||||||
|
$response = $this->get('/api/v1/me');
|
||||||
|
|
||||||
|
$response->assertStatus(401);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_logout_clears_refresh_token(): void
|
||||||
|
{
|
||||||
|
$password = 'LogoutTest_1';
|
||||||
|
$user = $this->createTestUser(['password' => $password]);
|
||||||
|
|
||||||
|
// 先登录获取 refresh_token
|
||||||
|
$this->post('/api/v1/login', [
|
||||||
|
'username' => $user->username,
|
||||||
|
'password' => $password,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
$this->assertNotNull($user->refresh_token);
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
$response = $this->get('/api/v1/logout', [], $this->authHeaders($user));
|
||||||
|
|
||||||
|
$response->assertStatus(200);
|
||||||
|
$response->assertJsonPath('code', 0);
|
||||||
|
|
||||||
|
$user->refresh();
|
||||||
|
$this->assertNull($user->refresh_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace HyperfTest\Cases\Unit\Model;
|
||||||
|
|
||||||
|
use App\Model\ApiKey;
|
||||||
|
use App\Model\User;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class ApiKeyTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function runInCoroutine(callable $callback): void
|
||||||
|
{
|
||||||
|
$exception = null;
|
||||||
|
\Swoole\Coroutine\run(static function () use ($callback, &$exception): void {
|
||||||
|
try {
|
||||||
|
$callback();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$exception = $e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if ($exception) {
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createTestUser(): User
|
||||||
|
{
|
||||||
|
$suffix = bin2hex(random_bytes(4));
|
||||||
|
|
||||||
|
return User::query()->create([
|
||||||
|
'username' => 'apikey_test_' . $suffix,
|
||||||
|
'password' => 'Pass_' . $suffix,
|
||||||
|
'email' => 'apikey_test_' . $suffix . '@example.com',
|
||||||
|
'status' => 1,
|
||||||
|
'api_key_enabled' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_generate_returns_plain_key_and_model(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$result = ApiKey::generate($user->id, 'Test Key');
|
||||||
|
|
||||||
|
$this->assertArrayHasKey('plain_key', $result);
|
||||||
|
$this->assertArrayHasKey('api_key', $result);
|
||||||
|
$this->assertInstanceOf(ApiKey::class, $result['api_key']);
|
||||||
|
$this->assertSame(64, strlen($result['plain_key']));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_generate_stores_sha256_hash(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$result = ApiKey::generate($user->id, 'Hash Test');
|
||||||
|
|
||||||
|
$expected_hash = hash('sha256', $result['plain_key']);
|
||||||
|
$this->assertSame($expected_hash, $result['api_key']->key_hash);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_generate_stores_prefix(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$result = ApiKey::generate($user->id, 'Prefix Test');
|
||||||
|
|
||||||
|
$expected_prefix = substr($result['plain_key'], 0, 8);
|
||||||
|
$this->assertSame($expected_prefix, $result['api_key']->key_prefix);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_find_by_plain_key_returns_valid_key(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$result = ApiKey::generate($user->id, 'Find Test');
|
||||||
|
|
||||||
|
$found = ApiKey::findByPlainKey($result['plain_key']);
|
||||||
|
$this->assertNotNull($found);
|
||||||
|
$this->assertSame($result['api_key']->id, $found->id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_find_by_plain_key_returns_null_for_invalid_key(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$found = ApiKey::findByPlainKey('invalid_key_that_does_not_exist');
|
||||||
|
$this->assertNull($found);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_find_by_plain_key_excludes_disabled_key(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$result = ApiKey::generate($user->id, 'Disabled Test');
|
||||||
|
|
||||||
|
$result['api_key']->enabled = false;
|
||||||
|
$result['api_key']->save();
|
||||||
|
|
||||||
|
$found = ApiKey::findByPlainKey($result['plain_key']);
|
||||||
|
$this->assertNull($found);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_find_by_plain_key_excludes_expired_key(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$result = ApiKey::generate($user->id, 'Expired Test', \Carbon\Carbon::now()->subDay()->toDateTimeString());
|
||||||
|
|
||||||
|
$found = ApiKey::findByPlainKey($result['plain_key']);
|
||||||
|
$this->assertNull($found);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_key_hash_is_hidden_in_json(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$result = ApiKey::generate($user->id, 'Hidden Test');
|
||||||
|
|
||||||
|
$json = $result['api_key']->toArray();
|
||||||
|
$this->assertArrayNotHasKey('key_hash', $json);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_is_valid_returns_true_for_active_key(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$result = ApiKey::generate($user->id, 'Valid Test');
|
||||||
|
|
||||||
|
$this->assertTrue($result['api_key']->isValid());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_is_valid_returns_false_for_disabled_key(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createTestUser();
|
||||||
|
$result = ApiKey::generate($user->id, 'Disabled Valid');
|
||||||
|
|
||||||
|
$result['api_key']->enabled = false;
|
||||||
|
$this->assertFalse($result['api_key']->isValid());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace HyperfTest\Cases\Unit\Model;
|
||||||
|
|
||||||
|
use App\Model\Role;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class RoleTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function runInCoroutine(callable $callback): void
|
||||||
|
{
|
||||||
|
$exception = null;
|
||||||
|
\Swoole\Coroutine\run(static function () use ($callback, &$exception): void {
|
||||||
|
try {
|
||||||
|
$callback();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$exception = $e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if ($exception) {
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_role_has_correct_fillable(): void
|
||||||
|
{
|
||||||
|
$role = new Role();
|
||||||
|
$this->assertEqualsCanonicalizing(['name', 'label', 'description'], $role->getFillable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_role_table_name(): void
|
||||||
|
{
|
||||||
|
$role = new Role();
|
||||||
|
$this->assertSame('roles', $role->getTable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_administrator_role_exists(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$role = Role::query()->where('name', 'administrator')->first();
|
||||||
|
$this->assertNotNull($role);
|
||||||
|
$this->assertSame('超级管理员', $role->label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_developer_role_exists(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$role = Role::query()->where('name', 'developer')->first();
|
||||||
|
$this->assertNotNull($role);
|
||||||
|
$this->assertSame('开发者', $role->label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_accessor_role_exists(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$role = Role::query()->where('name', 'accessor')->first();
|
||||||
|
$this->assertNotNull($role);
|
||||||
|
$this->assertSame('数据访问者', $role->label);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace HyperfTest\Cases\Unit\Model;
|
||||||
|
|
||||||
|
use App\Model\Role;
|
||||||
|
use App\Model\User;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
* @coversNothing
|
||||||
|
*/
|
||||||
|
class UserRoleTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function runInCoroutine(callable $callback): void
|
||||||
|
{
|
||||||
|
$exception = null;
|
||||||
|
\Swoole\Coroutine\run(static function () use ($callback, &$exception): void {
|
||||||
|
try {
|
||||||
|
$callback();
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
$exception = $e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if ($exception) {
|
||||||
|
throw $exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function createUserWithRole(string $role_name): User
|
||||||
|
{
|
||||||
|
$role = Role::query()->where('name', $role_name)->firstOrFail();
|
||||||
|
$suffix = bin2hex(random_bytes(4));
|
||||||
|
|
||||||
|
return User::query()->create([
|
||||||
|
'username' => 'role_test_' . $suffix,
|
||||||
|
'password' => 'Pass_' . $suffix,
|
||||||
|
'email' => 'role_test_' . $suffix . '@example.com',
|
||||||
|
'status' => 1,
|
||||||
|
'role_id' => $role->id,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_user_belongs_to_role(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createUserWithRole('administrator');
|
||||||
|
$this->assertNotNull($user->role);
|
||||||
|
$this->assertSame('administrator', $user->role->name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_is_administrator(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createUserWithRole('administrator');
|
||||||
|
$this->assertTrue($user->isAdministrator());
|
||||||
|
$this->assertFalse($user->isDeveloper());
|
||||||
|
$this->assertFalse($user->isAccessor());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_is_developer(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createUserWithRole('developer');
|
||||||
|
$this->assertFalse($user->isAdministrator());
|
||||||
|
$this->assertTrue($user->isDeveloper());
|
||||||
|
$this->assertFalse($user->isAccessor());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_is_accessor(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$user = $this->createUserWithRole('accessor');
|
||||||
|
$this->assertFalse($user->isAdministrator());
|
||||||
|
$this->assertFalse($user->isDeveloper());
|
||||||
|
$this->assertTrue($user->isAccessor());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_user_without_role(): void
|
||||||
|
{
|
||||||
|
$this->runInCoroutine(function (): void {
|
||||||
|
$suffix = bin2hex(random_bytes(4));
|
||||||
|
$user = User::query()->create([
|
||||||
|
'username' => 'no_role_' . $suffix,
|
||||||
|
'password' => 'Pass_' . $suffix,
|
||||||
|
'email' => 'no_role_' . $suffix . '@example.com',
|
||||||
|
'status' => 1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertNull($user->role);
|
||||||
|
$this->assertFalse($user->isAdministrator());
|
||||||
|
$this->assertFalse($user->isDeveloper());
|
||||||
|
$this->assertFalse($user->isAccessor());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user