Files
toolbox/tests/Unit/GitMonitorServiceTest.php
T
tradewindandCursor c45d68e201 #feature: Jenkins 构建入口与自动创建 release 分支
加入 crm-web/crm-opm 并可跳转 Jenkins 发布页;自动创建 release 时 commit 使用 release-版本号,并跳过本地 git hook,避免 agent-be 被拦下。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-14 09:14:57 +08:00

148 lines
6.2 KiB
PHP

<?php
namespace Tests\Unit;
use App\Services\DingTalkService;
use App\Services\GitMonitorService;
use Symfony\Component\Process\Process;
use Tests\TestCase;
class GitMonitorServiceTest extends TestCase
{
private array $tempPaths = [];
protected function tearDown(): void
{
foreach (array_reverse($this->tempPaths) as $path) {
if (is_dir($path)) {
$this->removeDirectory($path);
}
}
parent::tearDown();
}
public function test_auto_creating_release_branch_keeps_working_tree_untouched(): void
{
$workspacePath = $this->makeTempDirectory('toolbox-git-workspace-');
$remotePath = $this->makeTempDirectory('toolbox-git-remote-');
$repoPath = $workspacePath.DIRECTORY_SEPARATOR.'demo-repo';
$this->git($remotePath, ['git', 'init', '--bare']);
$this->git($workspacePath, ['git', 'clone', $remotePath, 'demo-repo']);
$this->git($repoPath, ['git', 'config', 'user.email', 'test@example.com']);
$this->git($repoPath, ['git', 'config', 'user.name', 'Test User']);
file_put_contents($repoPath.DIRECTORY_SEPARATOR.'version.txt', '1.0.0');
$this->git($repoPath, ['git', 'add', 'version.txt']);
$this->git($repoPath, ['git', 'commit', '-m', 'initial']);
$this->git($repoPath, ['git', 'branch', '-M', 'master']);
$this->git($repoPath, ['git', 'push', '-u', 'origin', 'master']);
$this->git($repoPath, ['git', 'checkout', '-b', 'develop']);
file_put_contents($repoPath.DIRECTORY_SEPARATOR.'work.txt', "draft\n");
$service = $this->makeGitMonitorService($workspacePath);
$method = new \ReflectionMethod($service, 'ensureReleaseBranchExists');
$method->invoke($service, 'demo-repo', ['directory' => 'demo-repo'], 'release/1.1.0', 'next release');
$this->assertSame('develop', $this->git($repoPath, ['git', 'branch', '--show-current']));
$this->assertSame("draft\n", file_get_contents($repoPath.DIRECTORY_SEPARATOR.'work.txt'));
$this->assertStringContainsString('?? work.txt', $this->git($repoPath, ['git', 'status', '--short']));
$this->assertNotEmpty($this->git($repoPath, ['git', 'ls-remote', '--heads', 'origin', 'release/1.1.0']));
$this->assertSame('1.1.0', $this->git($repoPath, ['git', 'show', 'origin/release/1.1.0:version.txt']));
$this->assertSame('release-1.1.0 next release', $this->git($repoPath, ['git', 'log', '-1', '--pretty=%s', 'origin/release/1.1.0']));
}
public function test_auto_creating_release_branch_skips_local_git_hooks(): void
{
$workspacePath = $this->makeTempDirectory('toolbox-git-workspace-');
$remotePath = $this->makeTempDirectory('toolbox-git-remote-');
$repoPath = $workspacePath.DIRECTORY_SEPARATOR.'demo-repo';
$this->git($remotePath, ['git', 'init', '--bare']);
$this->git($workspacePath, ['git', 'clone', $remotePath, 'demo-repo']);
$this->git($repoPath, ['git', 'config', 'user.email', 'test@example.com']);
$this->git($repoPath, ['git', 'config', 'user.name', 'Test User']);
file_put_contents($repoPath.DIRECTORY_SEPARATOR.'version.txt', '1.0.0');
$this->git($repoPath, ['git', 'add', 'version.txt']);
$this->git($repoPath, ['git', 'commit', '-m', 'initial']);
$this->git($repoPath, ['git', 'branch', '-M', 'master']);
$this->git($repoPath, ['git', 'push', '-u', 'origin', 'master']);
$hooksPath = $repoPath.DIRECTORY_SEPARATOR.'.git'.DIRECTORY_SEPARATOR.'hooks';
file_put_contents($hooksPath.DIRECTORY_SEPARATOR.'commit-msg', "#!/bin/sh\necho hook-blocked-commit >&2\nexit 1\n");
file_put_contents($hooksPath.DIRECTORY_SEPARATOR.'pre-push', "#!/bin/sh\necho hook-blocked-push >&2\nexit 1\n");
chmod($hooksPath.DIRECTORY_SEPARATOR.'commit-msg', 0755);
chmod($hooksPath.DIRECTORY_SEPARATOR.'pre-push', 0755);
$this->git($repoPath, ['git', 'config', 'core.hooksPath', $hooksPath]);
$service = $this->makeGitMonitorService($workspacePath);
$method = new \ReflectionMethod($service, 'ensureReleaseBranchExists');
$method->invoke($service, 'demo-repo', ['directory' => 'demo-repo'], 'release/1.1.0', 'next release');
$this->assertNotEmpty($this->git($repoPath, ['git', 'ls-remote', '--heads', 'origin', 'release/1.1.0']));
$this->assertSame('1.1.0', $this->git($repoPath, ['git', 'show', 'origin/release/1.1.0:version.txt']));
}
private function makeGitMonitorService(string $workspacePath): GitMonitorService
{
$reflection = new \ReflectionClass(GitMonitorService::class);
$service = $reflection->newInstanceWithoutConstructor();
$this->setProperty($service, 'projectsPath', $workspacePath);
$this->setProperty($service, 'gitTimeout', 60);
$this->setProperty($service, 'dingTalkService', $this->createMock(DingTalkService::class));
return $service;
}
private function setProperty(object $object, string $name, mixed $value): void
{
$property = new \ReflectionProperty($object, $name);
$property->setValue($object, $value);
}
private function makeTempDirectory(string $prefix): string
{
$path = sys_get_temp_dir().DIRECTORY_SEPARATOR.$prefix.bin2hex(random_bytes(6));
mkdir($path, 0777, true);
$this->tempPaths[] = $path;
return $path;
}
private function git(string $workingDirectory, array $command): string
{
$process = new Process($command, $workingDirectory);
$process->setTimeout(60);
$process->mustRun();
return trim($process->getOutput());
}
private function removeDirectory(string $path): void
{
$items = scandir($path);
if ($items === false) {
return;
}
foreach ($items as $item) {
if ($item === '.' || $item === '..') {
continue;
}
$itemPath = $path.DIRECTORY_SEPARATOR.$item;
if (is_dir($itemPath) && ! is_link($itemPath)) {
$this->removeDirectory($itemPath);
continue;
}
unlink($itemPath);
}
rmdir($path);
}
}