| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- <?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']);
- }
- }
|