OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

View File

@@ -0,0 +1,137 @@
<?php
namespace Unit\app\Domain\Help\Services;
use Leantime\Core\Exceptions\AuthorizationException;
use Leantime\Domain\Help\Services\FirstTaskStep;
use Leantime\Domain\Setting\Repositories\Setting as SettingRepository;
use Leantime\Domain\Tickets\Services\Tickets as TicketService;
use RuntimeException;
use Unit\TestCase;
/**
* Regression tests for the first-login onboarding dead-end in GH #3683.
*
* Readonly users have no TicketsPermissions::CREATE, so quickAddTicket() throws an
* AuthorizationException. That used to happen before the firstLoginCompleted flag was
* written, leaving the onboarding modal re-rendering forever with no way out. The flag
* write is the invariant here; the first task is only a convenience.
*/
class FirstTaskStepTest extends TestCase
{
use \Codeception\Test\Feature\Stub;
/** Captures every saveSetting() call as [key, value]. */
private array $savedSettings = [];
/** Captures every headline quickAddTicket() was called with. */
private array $createdHeadlines = [];
protected function setUp(): void
{
parent::setUp();
session(['userdata.id' => 1]);
$this->savedSettings = [];
$this->createdHeadlines = [];
}
/**
* Builds the step with a spying Setting repo and a Tickets service that either
* records the headline or throws the given exception.
*/
private function makeStep(?\Throwable $ticketFailure = null): FirstTaskStep
{
$settingsRepo = $this->make(SettingRepository::class, [
'saveSetting' => function ($key, $value) {
$this->savedSettings[] = [$key, $value];
return true;
},
]);
$ticketService = $this->make(TicketService::class, [
'quickAddTicket' => function ($params) use ($ticketFailure) {
if ($ticketFailure !== null) {
throw $ticketFailure;
}
$this->createdHeadlines[] = $params['headline'];
return 1;
},
]);
return new FirstTaskStep($settingsRepo, $ticketService);
}
/** The flag write, asserted as "written exactly once with true". */
private function assertOnboardingCompleted(): void
{
$this->assertSame(
[['user.1.firstLoginCompleted', true]],
$this->savedSettings,
'firstLoginCompleted must be persisted exactly once, regardless of ticket creation'
);
}
public function test_readonly_user_completes_onboarding_when_ticket_creation_is_denied(): void
{
$result = $this->makeStep(new AuthorizationException)->handle(['headline' => 'Water the plants']);
$this->assertTrue($result, 'handle() must report success even when the user cannot create tickets');
$this->assertOnboardingCompleted();
$this->assertSame([], $this->createdHeadlines);
}
public function test_unexpected_ticket_failure_still_completes_onboarding(): void
{
$result = $this->makeStep(new RuntimeException('database went away'))->handle(['headline' => 'Water the plants']);
$this->assertTrue($result);
$this->assertOnboardingCompleted();
}
public function test_permitted_user_creates_the_first_task_and_completes_onboarding(): void
{
$result = $this->makeStep()->handle(['headline' => 'Water the plants']);
$this->assertTrue($result);
$this->assertSame(['Water the plants'], $this->createdHeadlines);
$this->assertOnboardingCompleted();
}
public function test_headline_is_trimmed_before_the_task_is_created(): void
{
$this->makeStep()->handle(['headline' => ' Water the plants ']);
$this->assertSame(['Water the plants'], $this->createdHeadlines);
}
/**
* A blank, whitespace-only, missing or non-string headline must not create an empty
* task — but must still complete onboarding.
*
* @dataProvider blankHeadlineProvider
*/
public function test_blank_headline_skips_task_creation_but_completes_onboarding(array $params): void
{
$result = $this->makeStep()->handle($params);
$this->assertTrue($result);
$this->assertSame([], $this->createdHeadlines, 'No task should be created for a blank headline');
$this->assertOnboardingCompleted();
}
public static function blankHeadlineProvider(): array
{
return [
'empty string' => [['headline' => '']],
'whitespace only' => [['headline' => " \t "]],
'missing key' => [[]],
'null' => [['headline' => null]],
'array (malformed POST)' => [['headline' => ['nope']]],
];
}
}