ThinkAdmin/plugin/think-plugs-install/tests/ComposerLifecycleServiceTest.php
Anyon e7a8c05556 chore(repo): 统一 v8 仓库品牌名称
将 v8 重构分支中残留的 ThinkAdminDeveloper 文本统一调整为 ThinkAdmin,避免迁移到主仓库后继续暴露旧开发仓库名称。

主要内容:

- 更新 README 标题与项目描述。

- 统一 PHP 文件头注释中的项目标识。

- 同步调整测试、配置、插件与文档中的旧仓库名称文本。

- 保持旧包删除说明与架构边界测试语义不变,只清理品牌名称残留。
2026-05-08 16:15:24 +08:00

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);
}
}