#feature: improve Jira planning and release branch creation
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
<?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']));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,32 @@ class JiraServiceTest extends TestCase
|
||||
$this->assertEquals('本周完成的任务', $result['title']);
|
||||
}
|
||||
|
||||
public function test_build_next_week_tasks_jql_includes_developer_owner_for_requirements()
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->jiraService);
|
||||
$method = $reflection->getMethod('buildNextWeekTasksJql');
|
||||
|
||||
$result = $method->invoke($this->jiraService, 'test-user', ['需求已评审', '需求已排期', '开发中'], 'cf[11000]');
|
||||
|
||||
$this->assertEquals(
|
||||
'(assignee = "test-user" OR cf[11000] = "test-user") AND status IN ("需求已评审", "需求已排期", "开发中") AND issuetype in ("Story", "需求") AND Sprint is not EMPTY ORDER BY created ASC',
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
public function test_build_next_week_tasks_jql_keeps_assignee_only_when_owner_field_missing()
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->jiraService);
|
||||
$method = $reflection->getMethod('buildNextWeekTasksJql');
|
||||
|
||||
$result = $method->invoke($this->jiraService, 'test-user', ['需求已评审', '需求已排期', '开发中'], null);
|
||||
|
||||
$this->assertEquals(
|
||||
'assignee = "test-user" AND status IN ("需求已评审", "需求已排期", "开发中") AND issuetype in ("Story", "需求") AND Sprint is not EMPTY ORDER BY created ASC',
|
||||
$result
|
||||
);
|
||||
}
|
||||
|
||||
public function test_extract_sprint_info_from_string()
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->jiraService);
|
||||
|
||||
Reference in New Issue
Block a user