MenuOrderTest.php 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. <?php
  2. namespace Tests\Integration;
  3. use App\Services\MenuService;
  4. use Illuminate\Config\Repository;
  5. use Illuminate\Database\Capsule\Manager;
  6. use Illuminate\Database\Eloquent\Model;
  7. use Illuminate\Database\Schema\Blueprint;
  8. use Illuminate\Foundation\Application;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Facades\Facade;
  11. use PHPUnit\Framework\TestCase;
  12. class MenuOrderTest extends TestCase
  13. {
  14. protected function setUp(): void
  15. {
  16. Facade::clearResolvedInstances();
  17. Model::clearBootedModels();
  18. $app = new Application(dirname(__DIR__, 2));
  19. $app->instance('config', new Repository(['app' => ['url' => 'https://admin.example.test']]));
  20. Facade::setFacadeApplication($app);
  21. $db = new Manager($app);
  22. $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']);
  23. $db->setAsGlobal();
  24. $db->bootEloquent();
  25. $app->instance('db', $db->getDatabaseManager());
  26. $db->schema()->create('menus', function (Blueprint $t) {
  27. $t->id();
  28. $t->integer('parent_id')->default(0);
  29. $t->string('title');
  30. $t->integer('sort')->default(0);
  31. $t->integer('status')->default(1);
  32. $t->integer('type')->default(1);
  33. $t->string('uri')->default('');
  34. });
  35. $db->schema()->create('roles', function (Blueprint $t) {
  36. $t->id();
  37. $t->string('name');
  38. });
  39. foreach (['role_user' => 'user_id', 'role_menu' => 'menu_id'] as $table => $field) {
  40. $db->schema()->create($table, function (Blueprint $t) use ($field) {
  41. $t->id();
  42. $t->integer('role_id');
  43. $t->integer($field);
  44. });
  45. }
  46. }
  47. protected function tearDown(): void
  48. {
  49. DB::disconnect();
  50. Facade::clearResolvedInstances();
  51. Model::clearBootedModels();
  52. parent::tearDown();
  53. }
  54. private function menu(int $id, int $sort, int $parent = 0, int $status = 1, int $type = 1): void
  55. {
  56. DB::table('menus')->insert([
  57. 'id' => $id, 'title' => 'Menu ' . $id, 'sort' => $sort,
  58. 'parent_id' => $parent, 'status' => $status, 'type' => $type,
  59. ]);
  60. }
  61. private function role(int $id, array $menus, array $users): void
  62. {
  63. DB::table('roles')->insert(['id' => $id, 'name' => 'Role ' . $id]);
  64. foreach ($menus as $menuId) {
  65. DB::table('role_menu')->insert(['role_id' => $id, 'menu_id' => $menuId]);
  66. }
  67. foreach ($users as $userId) {
  68. DB::table('role_user')->insert(['role_id' => $id, 'user_id' => $userId]);
  69. }
  70. }
  71. private function treeIds($tree): array
  72. {
  73. $result = [];
  74. foreach ($tree as $node) {
  75. $result[] = ['id' => (int) $node['id'], 'children' => $this->treeIds($node['children'])];
  76. }
  77. return $result;
  78. }
  79. public function test_single_and_multiple_roles_follow_admin_order_for_roots_children_and_equal_sort(): void
  80. {
  81. foreach ([[10, 30], [20, 10], [30, 10]] as [$id, $sort]) {
  82. $this->menu($id, $sort);
  83. }
  84. foreach ([[50, 30], [60, 5], [70, 5], [80, 20]] as [$id, $sort]) {
  85. $this->menu($id, $sort, 20);
  86. }
  87. $this->role(1, [10, 30, 50, 70], [20]);
  88. $this->role(2, [20, 30, 60, 70, 80], [20]);
  89. $this->role(3, [70, 50, 30, 10, 80, 60, 20], [21]);
  90. $expected = $this->treeIds(MenuService::getUserMenu(1));
  91. $this->assertSame([20, 30, 10], array_column($expected, 'id'));
  92. $this->assertSame([60, 70, 80, 50], array_column($expected[0]['children'], 'id'));
  93. $this->assertSame($expected, $this->treeIds(MenuService::getUserMenu(20)));
  94. $this->assertSame($expected, $this->treeIds(MenuService::getUserMenu(21)));
  95. }
  96. public function test_sorting_preserves_role_permissions_existing_hidden_menus_and_menu_types(): void
  97. {
  98. $this->menu(10, 30);
  99. $this->menu(20, 10);
  100. $this->menu(30, 20, status: 2);
  101. $this->menu(40, 20, type: 2);
  102. $this->menu(50, 10, type: 2);
  103. $this->menu(60, 0, type: 2);
  104. $this->role(1, [10, 30, 40, 50], [20]);
  105. $this->role(2, [20], [21]);
  106. $this->assertSame([30, 10], array_column($this->treeIds(MenuService::getUserMenu(20)), 'id'));
  107. $this->assertSame([20], array_column($this->treeIds(MenuService::getUserMenu(21)), 'id'));
  108. $buttons = MenuService::getUserMenu(20, 2);
  109. $this->assertSame([50, 40], array_column($buttons, 'id'));
  110. $this->assertSame([], MenuService::getUserMenu(99));
  111. $this->assertSame([], MenuService::getUserMenu(99, 2));
  112. // Admin still uses its existing sort-only query and visible-menu filter.
  113. $this->assertSame([20, 10], array_column($this->treeIds(MenuService::getUserMenu(1)), 'id'));
  114. $this->assertSame([60, 50, 40], MenuService::getUserMenu(1, 2)->pluck('id')->all());
  115. }
  116. }