mirror of
https://gitee.com/zoujingli/ThinkAdmin.git
synced 2026-06-07 04:28:11 +08:00
将 v8 重构分支中残留的 ThinkAdminDeveloper 文本统一调整为 ThinkAdmin,避免迁移到主仓库后继续暴露旧开发仓库名称。 主要内容: - 更新 README 标题与项目描述。 - 统一 PHP 文件头注释中的项目标识。 - 同步调整测试、配置、插件与文档中的旧仓库名称文本。 - 保持旧包删除说明与架构边界测试语义不变,只清理品牌名称残留。
335 lines
13 KiB
PHP
335 lines
13 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
/**
|
|
* +----------------------------------------------------------------------
|
|
* | ThinkAdmin Plugin
|
|
* +----------------------------------------------------------------------
|
|
* | Copyright (c) 2014~2026 ThinkAdmin [ thinkadmin.top ]
|
|
* +----------------------------------------------------------------------
|
|
* | Official Website: https://thinkadmin.top
|
|
* +----------------------------------------------------------------------
|
|
* | Licensed: https://mit-license.org
|
|
* | Disclaimer: https://thinkadmin.top/disclaimer
|
|
* | Vip Rights: https://thinkadmin.top/vip-introduce
|
|
* +----------------------------------------------------------------------
|
|
* | Gitee Repository: https://gitee.com/zoujingli/ThinkAdmin
|
|
* | Github Repository: https://github.com/zoujingli/ThinkAdmin
|
|
* +----------------------------------------------------------------------
|
|
*/
|
|
|
|
namespace plugin\install\tests;
|
|
|
|
use PHPUnit\Framework\TestCase;
|
|
use plugin\install\service\ComposerLifecycleService;
|
|
|
|
/**
|
|
* @internal
|
|
* @coversNothing
|
|
*/
|
|
class ComposerLifecycleServiceTest extends TestCase
|
|
{
|
|
private string $root;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->root = sys_get_temp_dir() . '/thinkadmin-composer-install-' . bin2hex(random_bytes(6));
|
|
mkdir($this->root . '/vendor/composer', 0777, true);
|
|
file_put_contents($this->root . '/think', "#!/usr/bin/env php\n");
|
|
}
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
$this->removeTree($this->root);
|
|
}
|
|
|
|
public function testDiscoverInstalledPluginsReadsConfiguredMigrations(): void
|
|
{
|
|
$this->createInstalledPlugin('system', 'vendor/system-plugin', '系统管理', '20241010000001_install_system20241010.php');
|
|
$this->createInstalledPlugin('static', 'vendor/static-plugin', '静态资源');
|
|
|
|
$service = new ComposerLifecycleService($this->root);
|
|
$plugins = $service->discoverInstalledPlugins();
|
|
|
|
$this->assertCount(2, $plugins);
|
|
$this->assertTrue($plugins['vendor/system-plugin']['migrate']['configured']);
|
|
$this->assertSame('20241010000001_install_system20241010.php', $plugins['vendor/system-plugin']['migrate']['file']);
|
|
$this->assertFalse($plugins['vendor/static-plugin']['migrate']['configured']);
|
|
$this->assertArrayNotHasKey('signature', $plugins['vendor/system-plugin']['migrate']);
|
|
$this->assertNotSame('', $plugins['vendor/system-plugin']['signature']);
|
|
}
|
|
|
|
public function testDiscoverInstalledPluginsIncludesWorkspacePluginsOutsideInstalledJson(): void
|
|
{
|
|
$this->createWorkspacePlugin('worker', 'vendor/worker-plugin', '运行时服务', '20241010000008_install_worker20241010.php');
|
|
|
|
$service = new ComposerLifecycleService($this->root);
|
|
$plugins = $service->discoverInstalledPlugins();
|
|
|
|
$this->assertArrayHasKey('vendor/worker-plugin', $plugins);
|
|
$this->assertTrue($plugins['vendor/worker-plugin']['migrate']['configured']);
|
|
$this->assertSame('运行时服务', $plugins['vendor/worker-plugin']['title']);
|
|
}
|
|
|
|
public function testInstallFromCommandRunsAutoMigrateWhenConfigured(): void
|
|
{
|
|
$this->createInstalledPlugin('system', 'vendor/system-plugin', '系统管理', '20241010000001_install_system20241010.php');
|
|
|
|
$messages = [];
|
|
$commands = [];
|
|
$service = new ComposerLifecycleService(
|
|
$this->root,
|
|
function (string $message) use (&$messages): void {
|
|
$messages[] = $message;
|
|
},
|
|
function (array $command, string $cwd) use (&$commands): int {
|
|
$commands[] = [$command, $cwd];
|
|
return 0;
|
|
}
|
|
);
|
|
|
|
$status = $service->installFromCommand();
|
|
|
|
$this->assertSame(0, $status);
|
|
$this->assertSame([
|
|
[[PHP_BINARY, $this->root . '/think', 'service:discover'], $this->root],
|
|
[[PHP_BINARY, $this->root . '/think', 'xadmin:publish', '--migrate'], $this->root],
|
|
], $commands);
|
|
$this->assertContains('Install database migrations for: 系统管理', $messages);
|
|
$this->assertFileExists($this->root . '/vendor/' . ComposerLifecycleService::STATE_FILE);
|
|
}
|
|
|
|
public function testInstallFromCommandWritesForceMessageWhenForced(): void
|
|
{
|
|
$messages = [];
|
|
$commands = [];
|
|
$service = new ComposerLifecycleService(
|
|
$this->root,
|
|
function (string $message) use (&$messages): void {
|
|
$messages[] = $message;
|
|
},
|
|
function (array $command, string $cwd) use (&$commands): int {
|
|
$commands[] = [$command, $cwd];
|
|
return 0;
|
|
}
|
|
);
|
|
|
|
$status = $service->installFromCommand(false, true);
|
|
|
|
$this->assertSame(0, $status);
|
|
$this->assertSame([
|
|
[[PHP_BINARY, $this->root . '/think', 'service:discover'], $this->root],
|
|
[[PHP_BINARY, $this->root . '/think', 'xadmin:publish', '--migrate'], $this->root],
|
|
], $commands);
|
|
$this->assertContains('Force database migration for all published scripts.', $messages);
|
|
}
|
|
|
|
public function testWriteStateNormalizesLegacyPayload(): void
|
|
{
|
|
$service = new ComposerLifecycleService($this->root);
|
|
$service->writeState([
|
|
'vendor/system-plugin' => [
|
|
'name' => 'vendor/system-plugin',
|
|
'title' => '系统管理',
|
|
'version' => '1.0.0',
|
|
'reference' => 'ref-demo',
|
|
'signature' => 'sig-demo',
|
|
'migrate' => [
|
|
'configured' => true,
|
|
'file' => 'legacy.php',
|
|
],
|
|
],
|
|
]);
|
|
|
|
$state = json_decode((string)file_get_contents($this->root . '/vendor/' . ComposerLifecycleService::STATE_FILE), true, 512, JSON_THROW_ON_ERROR);
|
|
|
|
$this->assertSame([
|
|
'vendor/system-plugin' => [
|
|
'name' => 'vendor/system-plugin',
|
|
'title' => '系统管理',
|
|
'signature' => 'sig-demo',
|
|
'migrate' => [
|
|
'configured' => true,
|
|
],
|
|
],
|
|
], $state);
|
|
}
|
|
|
|
public function testSyncAfterComposerWarnsForMissingAndRemovedMigrations(): void
|
|
{
|
|
$this->createInstalledPlugin('system', 'vendor/system-plugin', '系统管理', '20241010000001_install_system20241010.php', '1.0.0', 'ref-new');
|
|
$this->createInstalledPlugin('static', 'vendor/static-plugin', '静态资源', null, '1.0.0', 'ref-static');
|
|
|
|
$messages = [];
|
|
$commands = [];
|
|
$service = new ComposerLifecycleService(
|
|
$this->root,
|
|
function (string $message) use (&$messages): void {
|
|
$messages[] = $message;
|
|
},
|
|
function (array $command, string $cwd) use (&$commands): int {
|
|
$commands[] = [$command, $cwd];
|
|
return 0;
|
|
}
|
|
);
|
|
|
|
$service->writeState([
|
|
'vendor/system-plugin' => [
|
|
'name' => 'vendor/system-plugin',
|
|
'title' => '系统管理',
|
|
'signature' => 'ref-old',
|
|
'migrate' => ['configured' => true],
|
|
],
|
|
'vendor/payment-plugin' => [
|
|
'name' => 'vendor/payment-plugin',
|
|
'title' => '支付管理',
|
|
'signature' => 'ref-pay',
|
|
'migrate' => ['configured' => true],
|
|
],
|
|
]);
|
|
|
|
$status = $service->syncAfterComposer();
|
|
|
|
$this->assertSame(0, $status);
|
|
$this->assertSame([
|
|
[[PHP_BINARY, $this->root . '/think', 'service:discover'], $this->root],
|
|
[[PHP_BINARY, $this->root . '/think', 'xadmin:publish', '--migrate'], $this->root],
|
|
], $commands);
|
|
$this->assertContains('Auto migrate plugins: 系统管理', $messages);
|
|
$this->assertContains('Skip database migration for plugins without extra.xadmin.migrate: 静态资源', $messages);
|
|
$this->assertContains('Removed plugins keep historical tables; rollback is not automatic: 支付管理', $messages);
|
|
}
|
|
|
|
public function testSyncAfterComposerDefersInitialMigrationUntilExplicitInstall(): void
|
|
{
|
|
$this->createInstalledPlugin('system', 'vendor/system-plugin', '系统管理', '20241010000001_install_system20241010.php', '1.0.0', 'ref-new');
|
|
$this->createInstalledPlugin('static', 'vendor/static-plugin', '静态资源', null, '1.0.0', 'ref-static');
|
|
|
|
$messages = [];
|
|
$commands = [];
|
|
$service = new ComposerLifecycleService(
|
|
$this->root,
|
|
function (string $message) use (&$messages): void {
|
|
$messages[] = $message;
|
|
},
|
|
function (array $command, string $cwd) use (&$commands): int {
|
|
$commands[] = [$command, $cwd];
|
|
return 0;
|
|
}
|
|
);
|
|
|
|
$status = $service->syncAfterComposer();
|
|
|
|
$this->assertSame(0, $status);
|
|
$this->assertSame([
|
|
[[PHP_BINARY, $this->root . '/think', 'service:discover'], $this->root],
|
|
[[PHP_BINARY, $this->root . '/think', 'xadmin:publish'], $this->root],
|
|
], $commands);
|
|
$this->assertContains('Skip automatic database migration on initial Composer install. Run `php think xadmin:install` after configuring the environment: 系统管理', $messages);
|
|
$this->assertContains('Skip database migration for plugins without extra.xadmin.migrate: 静态资源', $messages);
|
|
}
|
|
|
|
private function createInstalledPlugin(
|
|
string $code,
|
|
string $name,
|
|
string $title,
|
|
?string $migration = null,
|
|
string $version = '1.0.0',
|
|
string $reference = 'ref-demo'
|
|
): void {
|
|
$vendorPath = $this->root . '/vendor/' . dirname($name) . '/' . basename($name);
|
|
mkdir($vendorPath . '/src', 0777, true);
|
|
|
|
$manifest = [
|
|
'name' => $name,
|
|
'version' => $version,
|
|
'type' => 'think-admin-plugin',
|
|
'autoload' => ['psr-4' => ["plugin\\{$code}\\" => 'src']],
|
|
'extra' => [
|
|
'think' => ['services' => ["plugin\\{$code}\\Service"]],
|
|
'xadmin' => [
|
|
'app' => ['code' => $code, 'name' => $title],
|
|
],
|
|
],
|
|
];
|
|
|
|
if ($migration !== null) {
|
|
mkdir($vendorPath . '/stc/database', 0777, true);
|
|
file_put_contents($vendorPath . '/stc/database/' . $migration, "<?php\n\ndeclare(strict_types=1);\n");
|
|
$manifest['extra']['xadmin']['migrate'] = [
|
|
'file' => $migration,
|
|
'class' => 'Install' . ucfirst(str_replace('-', '', $code)),
|
|
'name' => ucfirst(str_replace('-', '', $code)) . 'Plugin',
|
|
];
|
|
}
|
|
|
|
file_put_contents($vendorPath . '/composer.json', json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
|
|
$installedFile = $this->root . '/vendor/composer/installed.json';
|
|
$data = is_file($installedFile) ? json_decode((string)file_get_contents($installedFile), true) : [];
|
|
$packages = is_array($data['packages'] ?? null) ? $data['packages'] : [];
|
|
$packages[] = [
|
|
'name' => $name,
|
|
'version' => $version,
|
|
'type' => 'think-admin-plugin',
|
|
'dist' => ['reference' => $reference],
|
|
'extra' => $manifest['extra'],
|
|
'install-path' => '../' . dirname($name) . '/' . basename($name),
|
|
];
|
|
file_put_contents($installedFile, json_encode(['packages' => $packages], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
}
|
|
|
|
private function createWorkspacePlugin(
|
|
string $code,
|
|
string $name,
|
|
string $title,
|
|
?string $migration = null
|
|
): void {
|
|
$pluginPath = $this->root . '/plugin/' . $code;
|
|
mkdir($pluginPath . '/src', 0777, true);
|
|
|
|
$manifest = [
|
|
'name' => $name,
|
|
'version' => '1.0.0',
|
|
'type' => 'think-admin-plugin',
|
|
'autoload' => ['psr-4' => ["plugin\\{$code}\\" => 'src']],
|
|
'extra' => [
|
|
'think' => ['services' => ["plugin\\{$code}\\Service"]],
|
|
'xadmin' => [
|
|
'app' => ['code' => $code, 'name' => $title],
|
|
],
|
|
],
|
|
];
|
|
|
|
if ($migration !== null) {
|
|
mkdir($pluginPath . '/stc/database', 0777, true);
|
|
file_put_contents($pluginPath . '/stc/database/' . $migration, "<?php\n\ndeclare(strict_types=1);\n");
|
|
$manifest['extra']['xadmin']['migrate'] = [
|
|
'file' => $migration,
|
|
'class' => 'Install' . ucfirst(str_replace('-', '', $code)),
|
|
'name' => ucfirst(str_replace('-', '', $code)) . 'Plugin',
|
|
];
|
|
}
|
|
|
|
file_put_contents($pluginPath . '/composer.json', json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
}
|
|
|
|
private function removeTree(string $path): void
|
|
{
|
|
if (!file_exists($path)) {
|
|
return;
|
|
}
|
|
if (is_file($path) || is_link($path)) {
|
|
@unlink($path);
|
|
return;
|
|
}
|
|
foreach (scandir($path) ?: [] as $item) {
|
|
if ($item === '.' || $item === '..') {
|
|
continue;
|
|
}
|
|
$this->removeTree($path . '/' . $item);
|
|
}
|
|
@rmdir($path);
|
|
}
|
|
}
|