295 lines
8.8 KiB
PHP
295 lines
8.8 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace HyperfTest\Cases\Unit\Model;
|
||
|
|
|
||
|
|
use App\Model\Role;
|
||
|
|
use App\Model\Route;
|
||
|
|
use App\Model\RouteGroup;
|
||
|
|
use App\Model\RoleRouteOverride;
|
||
|
|
use Hyperf\DbConnection\Db;
|
||
|
|
use PHPUnit\Framework\TestCase;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* @internal
|
||
|
|
* @coversNothing
|
||
|
|
*/
|
||
|
|
class RoutePermissionModelTest extends TestCase
|
||
|
|
{
|
||
|
|
protected function runInCoroutine(callable $callback): void
|
||
|
|
{
|
||
|
|
if (\Swoole\Coroutine::getCid() > 0) {
|
||
|
|
$callback();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
$exception = null;
|
||
|
|
\Swoole\Coroutine\run(static function () use ($callback, &$exception): void {
|
||
|
|
try {
|
||
|
|
$callback();
|
||
|
|
} catch (\Throwable $e) {
|
||
|
|
$exception = $e;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
if ($exception) {
|
||
|
|
throw $exception;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- RouteGroup 模型测试 ---
|
||
|
|
|
||
|
|
public function test_route_group_fillable(): void
|
||
|
|
{
|
||
|
|
$group = new RouteGroup();
|
||
|
|
$this->assertEqualsCanonicalizing(
|
||
|
|
['name', 'label', 'description', 'sort_order'],
|
||
|
|
$group->getFillable()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_route_group_table_name(): void
|
||
|
|
{
|
||
|
|
$group = new RouteGroup();
|
||
|
|
$this->assertSame('route_groups', $group->getTable());
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_route_group_has_no_timestamps(): void
|
||
|
|
{
|
||
|
|
$group = new RouteGroup();
|
||
|
|
$this->assertFalse($group->timestamps);
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- Route 模型测试 ---
|
||
|
|
|
||
|
|
public function test_route_fillable(): void
|
||
|
|
{
|
||
|
|
$route = new Route();
|
||
|
|
$this->assertEqualsCanonicalizing(
|
||
|
|
['group_id', 'method', 'path', 'name', 'label'],
|
||
|
|
$route->getFillable()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_route_table_name(): void
|
||
|
|
{
|
||
|
|
$route = new Route();
|
||
|
|
$this->assertSame('routes', $route->getTable());
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_route_has_no_timestamps(): void
|
||
|
|
{
|
||
|
|
$route = new Route();
|
||
|
|
$this->assertFalse($route->timestamps);
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- RoleRouteOverride 模型测试 ---
|
||
|
|
|
||
|
|
public function test_role_route_override_fillable(): void
|
||
|
|
{
|
||
|
|
$override = new RoleRouteOverride();
|
||
|
|
$this->assertEqualsCanonicalizing(
|
||
|
|
['role_id', 'route_id', 'allowed'],
|
||
|
|
$override->getFillable()
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_role_route_override_table_name(): void
|
||
|
|
{
|
||
|
|
$override = new RoleRouteOverride();
|
||
|
|
$this->assertSame('role_route_overrides', $override->getTable());
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_role_route_override_casts_allowed_to_boolean(): void
|
||
|
|
{
|
||
|
|
$override = new RoleRouteOverride();
|
||
|
|
$casts = $override->getCasts();
|
||
|
|
$this->assertSame('boolean', $casts['allowed']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- 关联测试(需要数据库) ---
|
||
|
|
|
||
|
|
public function test_route_group_has_many_routes(): void
|
||
|
|
{
|
||
|
|
$this->runInCoroutine(function (): void {
|
||
|
|
Db::beginTransaction();
|
||
|
|
try {
|
||
|
|
$group = RouteGroup::query()->create([
|
||
|
|
'name' => 'test_group_' . uniqid(),
|
||
|
|
'label' => '测试分组',
|
||
|
|
]);
|
||
|
|
|
||
|
|
Route::query()->create([
|
||
|
|
'group_id' => $group->id,
|
||
|
|
'method' => 'GET',
|
||
|
|
'path' => '/api/v1/test-' . uniqid(),
|
||
|
|
]);
|
||
|
|
Route::query()->create([
|
||
|
|
'group_id' => $group->id,
|
||
|
|
'method' => 'POST',
|
||
|
|
'path' => '/api/v1/test-' . uniqid(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
$routes = $group->routes;
|
||
|
|
$this->assertCount(2, $routes);
|
||
|
|
} finally {
|
||
|
|
Db::rollBack();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_route_belongs_to_group(): void
|
||
|
|
{
|
||
|
|
$this->runInCoroutine(function (): void {
|
||
|
|
Db::beginTransaction();
|
||
|
|
try {
|
||
|
|
$group = RouteGroup::query()->create([
|
||
|
|
'name' => 'test_group_' . uniqid(),
|
||
|
|
'label' => '测试分组',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$route = Route::query()->create([
|
||
|
|
'group_id' => $group->id,
|
||
|
|
'method' => 'GET',
|
||
|
|
'path' => '/api/v1/test-' . uniqid(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
$this->assertNotNull($route->group);
|
||
|
|
$this->assertSame($group->id, $route->group->id);
|
||
|
|
} finally {
|
||
|
|
Db::rollBack();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_role_has_many_route_groups(): void
|
||
|
|
{
|
||
|
|
$this->runInCoroutine(function (): void {
|
||
|
|
Db::beginTransaction();
|
||
|
|
try {
|
||
|
|
$role = Role::query()->where('name', 'developer')->first();
|
||
|
|
|
||
|
|
$group1 = RouteGroup::query()->create([
|
||
|
|
'name' => 'test_group_a_' . uniqid(),
|
||
|
|
'label' => '测试分组 A',
|
||
|
|
]);
|
||
|
|
$group2 = RouteGroup::query()->create([
|
||
|
|
'name' => 'test_group_b_' . uniqid(),
|
||
|
|
'label' => '测试分组 B',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$role->routeGroups()->attach([$group1->id, $group2->id]);
|
||
|
|
|
||
|
|
// 重新加载关联
|
||
|
|
$role->load('routeGroups');
|
||
|
|
$this->assertCount(2, $role->routeGroups);
|
||
|
|
|
||
|
|
$group_ids = $role->routeGroups->pluck('id')->toArray();
|
||
|
|
$this->assertContains($group1->id, $group_ids);
|
||
|
|
$this->assertContains($group2->id, $group_ids);
|
||
|
|
} finally {
|
||
|
|
Db::rollBack();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_role_route_override_query(): void
|
||
|
|
{
|
||
|
|
$this->runInCoroutine(function (): void {
|
||
|
|
Db::beginTransaction();
|
||
|
|
try {
|
||
|
|
$role = Role::query()->where('name', 'accessor')->first();
|
||
|
|
|
||
|
|
$route = Route::query()->create([
|
||
|
|
'method' => 'DELETE',
|
||
|
|
'path' => '/api/v1/test-override-' . uniqid(),
|
||
|
|
]);
|
||
|
|
|
||
|
|
RoleRouteOverride::query()->create([
|
||
|
|
'role_id' => $role->id,
|
||
|
|
'route_id' => $route->id,
|
||
|
|
'allowed' => false,
|
||
|
|
]);
|
||
|
|
|
||
|
|
// 查询角色的覆盖规则
|
||
|
|
$overrides = $role->routeOverrides;
|
||
|
|
$this->assertGreaterThanOrEqual(1, $overrides->count());
|
||
|
|
|
||
|
|
$override = $overrides->where('route_id', $route->id)->first();
|
||
|
|
$this->assertNotNull($override);
|
||
|
|
$this->assertFalse($override->allowed);
|
||
|
|
|
||
|
|
// 查询路由的覆盖规则
|
||
|
|
$route_overrides = $route->overrides;
|
||
|
|
$this->assertCount(1, $route_overrides);
|
||
|
|
$this->assertSame($role->id, $route_overrides->first()->role_id);
|
||
|
|
} finally {
|
||
|
|
Db::rollBack();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_route_method_path_unique_constraint(): void
|
||
|
|
{
|
||
|
|
$this->runInCoroutine(function (): void {
|
||
|
|
Db::beginTransaction();
|
||
|
|
try {
|
||
|
|
$unique_path = '/api/v1/unique-test-' . uniqid();
|
||
|
|
|
||
|
|
Route::query()->create([
|
||
|
|
'method' => 'GET',
|
||
|
|
'path' => $unique_path,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$this->expectException(\Throwable::class);
|
||
|
|
Route::query()->create([
|
||
|
|
'method' => 'GET',
|
||
|
|
'path' => $unique_path,
|
||
|
|
]);
|
||
|
|
} finally {
|
||
|
|
Db::rollBack();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_route_group_roles_inverse_relation(): void
|
||
|
|
{
|
||
|
|
$this->runInCoroutine(function (): void {
|
||
|
|
Db::beginTransaction();
|
||
|
|
try {
|
||
|
|
$role = Role::query()->where('name', 'developer')->first();
|
||
|
|
$group = RouteGroup::query()->create([
|
||
|
|
'name' => 'test_inverse_' . uniqid(),
|
||
|
|
'label' => '反向关联测试',
|
||
|
|
]);
|
||
|
|
|
||
|
|
$role->routeGroups()->attach($group->id);
|
||
|
|
|
||
|
|
// 从 RouteGroup 查询关联的 Role
|
||
|
|
$group->load('roles');
|
||
|
|
$this->assertCount(1, $group->roles);
|
||
|
|
$this->assertSame($role->id, $group->roles->first()->id);
|
||
|
|
} finally {
|
||
|
|
Db::rollBack();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public function test_routes_synced_from_command(): void
|
||
|
|
{
|
||
|
|
$this->runInCoroutine(function (): void {
|
||
|
|
// route:sync 已执行,验证 routes 表有数据
|
||
|
|
$count = Route::query()->count();
|
||
|
|
$this->assertGreaterThan(0, $count);
|
||
|
|
|
||
|
|
// 验证已知路由存在
|
||
|
|
$route = Route::query()
|
||
|
|
->where('method', 'GET')
|
||
|
|
->where('path', '/api/v1/users')
|
||
|
|
->first();
|
||
|
|
$this->assertNotNull($route);
|
||
|
|
$this->assertNotNull($route->name);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|