|
@@ -0,0 +1,148 @@
|
|
|
|
|
+<?php
|
|
|
|
|
+
|
|
|
|
|
+namespace Tests\Integration;
|
|
|
|
|
+
|
|
|
|
|
+use App\Http\Controllers\admin\Menu as MenuController;
|
|
|
|
|
+use App\Models\Menu;
|
|
|
|
|
+use Illuminate\Config\Repository;
|
|
|
|
|
+use Illuminate\Database\Capsule\Manager;
|
|
|
|
|
+use Illuminate\Database\Schema\Blueprint;
|
|
|
|
|
+use Illuminate\Foundation\Application;
|
|
|
|
|
+use Illuminate\Http\JsonResponse;
|
|
|
|
|
+use Illuminate\Http\Request;
|
|
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
|
|
+use Illuminate\Support\Facades\Facade;
|
|
|
|
|
+use Illuminate\Translation\ArrayLoader;
|
|
|
|
|
+use Illuminate\Translation\Translator;
|
|
|
|
|
+use Illuminate\Validation\Factory;
|
|
|
|
|
+use PHPUnit\Framework\TestCase;
|
|
|
|
|
+
|
|
|
|
|
+class MenuIconTest extends TestCase
|
|
|
|
|
+{
|
|
|
|
|
+ private Application $app;
|
|
|
|
|
+
|
|
|
|
|
+ protected function setUp(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ Facade::clearResolvedInstances();
|
|
|
|
|
+ require_once dirname(__DIR__, 2) . '/app/Helpers/helpers.php';
|
|
|
|
|
+ $this->app = new Application(dirname(__DIR__, 2));
|
|
|
|
|
+ $this->app->instance('config', new Repository(['app' => ['url' => 'https://admin.example.test', 'locale' => 'zh']]));
|
|
|
|
|
+ Facade::setFacadeApplication($this->app);
|
|
|
|
|
+ $db = new Manager($this->app);
|
|
|
|
|
+ $db->addConnection(['driver' => 'sqlite', 'database' => ':memory:', 'prefix' => 'bot_']);
|
|
|
|
|
+ $db->setAsGlobal();
|
|
|
|
|
+ $db->bootEloquent();
|
|
|
|
|
+ $this->app->instance('db', $db->getDatabaseManager());
|
|
|
|
|
+ $this->app->bind('db.schema', fn () => $db->schema());
|
|
|
|
|
+ $translator = new Translator(new ArrayLoader(), 'zh');
|
|
|
|
|
+ $this->app->instance('translator', $translator);
|
|
|
|
|
+ $this->app->instance('validator', new Factory($translator, $this->app));
|
|
|
|
|
+ $this->app->instance(\Illuminate\Contracts\Routing\ResponseFactory::class, new class {
|
|
|
|
|
+ public function json($data, $status = 200, $headers = [], $options = 0) {
|
|
|
|
|
+ return new JsonResponse($data, $status, $headers, $options);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ Request::macro('validate', function (array $rules) {
|
|
|
|
|
+ return app('validator')->make($this->all(), $rules)->validate();
|
|
|
|
|
+ });
|
|
|
|
|
+ $db->schema()->create('menus', function (Blueprint $t) {
|
|
|
|
|
+ $t->id(); $t->unsignedBigInteger('parent_id')->default(0); $t->string('title');
|
|
|
|
|
+ $t->string('icon', 50)->nullable(); $t->string('uri'); $t->string('permission_name')->nullable();
|
|
|
|
|
+ $t->integer('sort')->default(0); $t->integer('status')->default(1); $t->integer('type')->default(1); $t->timestamps();
|
|
|
|
|
+ });
|
|
|
|
|
+ (require dirname(__DIR__, 2) . '/database/migrations/2026_10_29_120000_expand_menu_icon_for_uploaded_images.php')->up();
|
|
|
|
|
+ foreach (['roles' => ['name', 'display_name', 'description'], 'role_user' => ['user_id', 'role_id'], 'role_menu' => ['menu_id', 'role_id']] as $table => $fields) {
|
|
|
|
|
+ $db->schema()->create($table, function (Blueprint $t) use ($fields) {
|
|
|
|
|
+ $t->id();
|
|
|
|
|
+ foreach ($fields as $field) $t->string($field)->nullable();
|
|
|
|
|
+ $t->timestamps();
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ $this->request('/admin/menu/all');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected function tearDown(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ DB::disconnect();
|
|
|
|
|
+ Facade::clearResolvedInstances();
|
|
|
|
|
+ parent::tearDown();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function request(string $path, array $params = [], string $method = 'GET'): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->app->instance('request', Request::create($path, $method, $params));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function save(array $params = []): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->request('/admin/menu/submit', $params + ['title' => '报表', 'uri' => '/reports', 'parent_id' => 0, 'status' => 1, 'sort' => 0, 'type' => 1], 'POST');
|
|
|
|
|
+ return (new MenuController())->store()->getData(true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private function allMenus(): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->request('/admin/menu/all');
|
|
|
|
|
+ $body = (new MenuController())->all()->getData(true);
|
|
|
|
|
+ $this->assertSame(0, $body['code']);
|
|
|
|
|
+ return $body['data'];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_uploaded_image_roundtrips_in_parent_and_child_menu_nodes(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $path = '/storage/images/' . str_repeat('12345678', 8) . '.png';
|
|
|
|
|
+ $url = 'https://admin.example.test' . $path;
|
|
|
|
|
+ $this->assertSame(0, $this->save(['icon' => $url])['code']);
|
|
|
|
|
+ $parent = (int) DB::table('menus')->value('id');
|
|
|
|
|
+ $this->assertSame($path, DB::table('menus')->where('id', $parent)->value('icon'));
|
|
|
|
|
+ $this->assertSame(0, $this->save(['title' => '子菜单', 'uri' => '/reports/daily', 'parent_id' => $parent, 'icon' => '/storage/images/child.webp'])['code']);
|
|
|
|
|
+ $tree = $this->allMenus();
|
|
|
|
|
+ $this->assertSame($url, $tree[0]['icon']);
|
|
|
|
|
+ $this->assertSame('https://admin.example.test/storage/images/child.webp', $tree[0]['children'][0]['icon']);
|
|
|
|
|
+ $this->assertSame('/reports/daily', $tree[0]['children'][0]['uri']);
|
|
|
|
|
+ $this->assertSame(0, $this->save(['id' => $parent, 'icon' => $tree[0]['icon']])['code']);
|
|
|
|
|
+ $this->assertSame($path, DB::table('menus')->where('id', $parent)->value('icon'));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_icon_can_be_retained_replaced_with_cdn_url_and_cleared(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ $this->assertSame(0, $this->save(['icon' => '/storage/images/menu.png'])['code']);
|
|
|
|
|
+ $id = (int) DB::table('menus')->value('id');
|
|
|
|
|
+ $this->assertSame(0, $this->save(['id' => $id, 'title' => '改名'])['code']);
|
|
|
|
|
+ $this->assertSame('https://admin.example.test/storage/images/menu.png', $this->allMenus()[0]['icon']);
|
|
|
|
|
+ $url = 'https://cdn.example.test/menu.png?version=2';
|
|
|
|
|
+ $this->assertSame(0, $this->save(['id' => $id, 'icon' => $url])['code']);
|
|
|
|
|
+ $this->assertSame($url, $this->allMenus()[0]['icon']);
|
|
|
|
|
+ foreach ([null, ''] as $empty) {
|
|
|
|
|
+ $this->assertSame(0, $this->save(['id' => $id, 'icon' => '/storage/images/menu.png'])['code']);
|
|
|
|
|
+ $this->assertSame(0, $this->save(['id' => $id, 'icon' => $empty])['code']);
|
|
|
|
|
+ $this->assertSame('', $this->allMenus()[0]['icon']);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_class_names_invalid_values_and_oversized_icons_are_rejected_without_writes(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ foreach (['ri-home-line', 'fa fa-home', 'javascript:alert(1)', '//other.example.test/icon.png', ['icon'], 'https://cdn.example.test/' . str_repeat('x', 500)] as $value) {
|
|
|
|
|
+ $this->assertNotSame(0, $this->save(['icon' => $value])['code']);
|
|
|
|
|
+ $this->assertSame(0, DB::table('menus')->count());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public function test_existing_class_icons_are_empty_and_role_menus_use_uploaded_images(): void
|
|
|
|
|
+ {
|
|
|
|
|
+ DB::table('menus')->insert(['id' => 1, 'parent_id' => 0, 'title' => '旧菜单', 'uri' => '/legacy', 'icon' => 'ri-home-line']);
|
|
|
|
|
+ $this->assertSame(0, $this->save(['icon' => '/storage/images/allowed.png'])['code']);
|
|
|
|
|
+ $id = (int) DB::table('menus')->where('uri', '/reports')->value('id');
|
|
|
|
|
+ $this->assertSame('', $this->allMenus()[0]['icon']);
|
|
|
|
|
+ $this->assertSame('ri-home-line', DB::table('menus')->where('id', 1)->value('icon'));
|
|
|
|
|
+ DB::table('roles')->insert(['id' => 2, 'name' => 'viewer']);
|
|
|
|
|
+ DB::table('role_user')->insert(['user_id' => 20, 'role_id' => 2]);
|
|
|
|
|
+ DB::table('role_menu')->insert(['menu_id' => $id, 'role_id' => 2]);
|
|
|
|
|
+ $this->request('/admin/menu/mineMenu');
|
|
|
|
|
+ request()->user = (object) ['id' => 20];
|
|
|
|
|
+ $body = (new MenuController())->mineMenu()->getData(true);
|
|
|
|
|
+ $this->assertSame(0, $body['code']);
|
|
|
|
|
+ $this->assertCount(1, $body['data']);
|
|
|
|
|
+ $this->assertSame($id, $body['data'][0]['id']);
|
|
|
|
|
+ $this->assertSame('https://admin.example.test/storage/images/allowed.png', $body['data'][0]['icon']);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|