originRuntimeEnvFile !== '') { $this->setRuntimeEnvFile($this->originRuntimeEnvFile); } sysvar('think.admin.runtime', []); parent::tearDown(); } public function testEditorUpdatesBaseEditorConfigAndWritesOplogForSuperAdmin(): void { $result = $this->callActionController('editor', ['editor' => 'tinymce'], true); $oplog = SystemOplog::mk()->order('id desc')->findOrEmpty(); $this->assertSame(200, intval($result['code'] ?? 0)); $this->assertSame('已切换后台编辑器!', $result['info'] ?? ''); $this->assertStringContainsString('javascript:location.reload()', strval($result['data'] ?? '')); $this->assertSame('tinymce', ConfigService::getEditorDriver()); $this->assertTrue($oplog->isExists()); $this->assertSame('系统运维管理', $oplog->getData('action')); $this->assertStringContainsString('切换编辑器为', $oplog->getData('content')); $this->assertSame('admin', $oplog->getData('username')); } public function testEditorRejectsNonSuperAdmin(): void { $result = $this->callActionController('editor', ['editor' => 'tinymce'], false); $this->assertSame(500, intval($result['code'] ?? 1)); $this->assertSame('请使用超管账号操作!', $result['info'] ?? ''); $this->assertSame(0, SystemOplog::mk()->count()); } public function testConfigCompactsRowsWithoutLosingValuesForSuperAdmin(): void { $result = $this->callActionController('config', [], true); $oplog = SystemOplog::mk()->order('id desc')->findOrEmpty(); $this->assertSame(200, intval($result['code'] ?? 0)); $this->assertSame('清理系统配置成功!', $result['info'] ?? ''); $this->assertTrue($oplog->isExists()); $this->assertSame('系统运维管理', $oplog->getData('action')); $this->assertSame('清理系统配置参数', $oplog->getData('content')); $this->assertSame('admin', $oplog->getData('username')); } public function testConfigRejectsNonSuperAdmin(): void { $result = $this->callActionController('config', [], false); $this->assertSame(500, intval($result['code'] ?? 1)); $this->assertSame('请使用超管账号操作!', $result['info'] ?? ''); $this->assertSame(0, SystemOplog::mk()->count()); } public function testPushCallsOptimizeSchemaAndWritesOplogForSuperAdmin(): void { $console = $this->bindConsole(); $result = $this->callActionController('push', [], true); $oplog = SystemOplog::mk()->order('id desc')->findOrEmpty(); $this->assertSame(200, intval($result['code'] ?? 0)); $this->assertSame('网站缓存加速成功!', $result['info'] ?? ''); $this->assertStringContainsString('javascript:location.reload()', strval($result['data'] ?? '')); $this->assertCount(1, $console->calls); $this->assertSame('optimize:schema', $console->calls[0]['command']); $this->assertSame(['--connection=' . $this->connectionName], $console->calls[0]['parameters']); $this->assertStringContainsString('mode = product', file_get_contents($this->runtimeEnvFile) ?: ''); $this->assertTrue($oplog->isExists()); $this->assertSame('系统运维管理', $oplog->getData('action')); $this->assertSame('刷新发布运行缓存', $oplog->getData('content')); $this->assertSame('admin', $oplog->getData('username')); } public function testClearInvokesClearCommandPreservesModeAndWritesOplogForSuperAdmin(): void { RuntimeService::set('product'); $this->app->cache->set('runtime-clear-key', 'present'); $console = $this->bindConsole(); $result = $this->callActionController('clear', [], true); $oplog = SystemOplog::mk()->order('id desc')->findOrEmpty(); $this->assertSame(200, intval($result['code'] ?? 0)); $this->assertSame('清空日志缓存成功!', $result['info'] ?? ''); $this->assertCount(1, $console->calls); $this->assertSame('clear', $console->calls[0]['command']); $this->assertSame(['--dir'], $console->calls[0]['parameters']); $this->assertNull($this->app->cache->get('runtime-clear-key')); $this->assertStringContainsString('mode = product', file_get_contents($this->runtimeEnvFile) ?: ''); $this->assertTrue($oplog->isExists()); $this->assertSame('系统运维管理', $oplog->getData('action')); $this->assertSame('清理网站日志缓存', $oplog->getData('content')); $this->assertSame('admin', $oplog->getData('username')); } public function testDebugSwitchesRuntimeModesAndWritesOplogForSuperAdmin(): void { $toProduct = $this->callActionController('debug', ['state' => '1'], true); $productOplog = SystemOplog::mk()->order('id desc')->findOrEmpty(); $this->assertSame(200, intval($toProduct['code'] ?? 0)); $this->assertSame('已切换为生产模式!', $toProduct['info'] ?? ''); $this->assertStringContainsString('mode = product', file_get_contents($this->runtimeEnvFile) ?: ''); $this->assertSame('开发模式切换为生产模式', $productOplog->getData('content')); $toDebug = $this->callActionController('debug', [], true); $debugOplog = SystemOplog::mk()->order('id desc')->findOrEmpty(); $this->assertSame(200, intval($toDebug['code'] ?? 0)); $this->assertSame('已切换为开发模式!', $toDebug['info'] ?? ''); $this->assertStringContainsString('mode = debug', file_get_contents($this->runtimeEnvFile) ?: ''); $this->assertSame('生产模式切换为开发模式', $debugOplog->getData('content')); $this->assertSame('admin', $debugOplog->getData('username')); } public function testPushRejectsNonSuperAdmin(): void { $console = $this->bindConsole(); $result = $this->callActionController('push', [], false); $this->assertSame(500, intval($result['code'] ?? 1)); $this->assertSame('请使用超管账号操作!', $result['info'] ?? ''); $this->assertCount(0, $console->calls); $this->assertFalse(is_file($this->runtimeEnvFile)); $this->assertSame(0, SystemOplog::mk()->count()); } public function testPushRejectsNonSuperAdminInEnglishWhenLangSetIsEnUs(): void { $this->switchSystemLang('en-us'); $result = $this->callActionController('push', [], false); $this->assertSame(500, intval($result['code'] ?? 1)); $this->assertSame('Please use a super-admin account to perform this action.', $result['info'] ?? ''); } protected function defineSchema(): void { $this->createSystemDataTable(); $this->createSystemOplogTable(); } protected function afterSchemaCreated(): void { $context = new PluginSystemContext(); Container::getInstance()->instance(SystemContextInterface::class, $context); $this->app->instance(SystemContextInterface::class, $context); $this->originRuntimeEnvFile = $this->getRuntimeEnvFile(); $this->runtimeEnvFile = $this->sandboxPath . '/runtime/.env'; is_dir(dirname($this->runtimeEnvFile)) || mkdir(dirname($this->runtimeEnvFile), 0777, true); $this->setRuntimeEnvFile($this->runtimeEnvFile); sysvar('think.admin.runtime', []); } private function callActionController(string $action, array $payload = [], bool $super = true): array { $request = (new Request()) ->withGet($payload) ->withPost($payload) ->setMethod('POST') ->setController('system') ->setAction($action); $this->bindAdminUser($super); $this->setRequestPayload($request, $payload); $this->app->instance('request', $request); try { $controller = new SystemController($this->app); $controller->{$action}(); self::fail("Expected SystemController::{$action} to throw HttpResponseException."); } catch (HttpResponseException $exception) { return json_decode($exception->getResponse()->getContent(), true) ?: []; } } private function bindAdminUser(bool $super): void { RequestContext::instance()->setAuth([ 'id' => $super ? 10000 : 9101, 'username' => $super ? 'admin' : 'tester', 'password' => $this->hashSystemPassword('changed-password'), ], '', true); } private function bindConsole(): object { $console = new class { public array $calls = []; public function call(string $command, array $parameters = [], ?string $scene = null): object { $this->calls[] = compact('command', 'parameters', 'scene'); return new class { public function fetch(): string { return ''; } }; } }; $this->app->instance('console', $console); return $console; } private function getRuntimeEnvFile(): string { $property = new \ReflectionProperty(RuntimeService::class, 'envFile'); $property->setAccessible(true); return strval($property->getValue()); } private function setRuntimeEnvFile(string $path): void { $property = new \ReflectionProperty(RuntimeService::class, 'envFile'); $property->setAccessible(true); $property->setValue($path); } private function setRequestPayload(Request $request, array $data): void { $property = new \ReflectionProperty(Request::class, 'request'); $property->setAccessible(true); $property->setValue($request, $data); } private function switchSystemLang(string $langSet): void { $this->app->lang->switchLangSet($langSet); LangService::load($this->app, $langSet); } }