Files
breadcrumb-the-shire/modules/helpdesk/tests/Module/Helpdesk/Service/HandoverRevisionServiceTest.php
fs e7c60468c9 feat(helpdesk): add domain linking, bulk actions, and revision polish to handovers
Adds domain selection (cascaded from debitor) to handover creation and edit,
bulk delete with confirmation dialog, and various UI improvements to the
handover wizard and list page. Includes migration for domain columns,
domain-select AJAX endpoint, and updated tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 13:21:12 +02:00

261 lines
11 KiB
PHP

<?php
namespace MintyPHP\Tests\Module\Helpdesk\Service;
use MintyPHP\Module\Helpdesk\Repository\HandoverRepository;
use MintyPHP\Module\Helpdesk\Repository\HandoverRevisionRepository;
use MintyPHP\Module\Helpdesk\Service\HandoverRevisionService;
use MintyPHP\Module\Helpdesk\Service\HandoverService;
use PHPUnit\Framework\TestCase;
class HandoverRevisionServiceTest extends TestCase
{
private const TENANT_ID = 1;
private const USER_ID = 42;
private const HANDOVER_ID = 10;
private function createService(
?HandoverRevisionRepository $revisionRepo = null,
?HandoverRepository $handoverRepo = null,
): HandoverRevisionService {
$revisionRepo = $revisionRepo ?? $this->createMock(HandoverRevisionRepository::class);
$handoverRepo = $handoverRepo ?? $this->createMock(HandoverRepository::class);
return new HandoverRevisionService($revisionRepo, $handoverRepo);
}
// ── createRevision ───────────────────────────────────────────────
public function testCreateRevisionIncrementsNumber(): void
{
$revisionRepo = $this->createMock(HandoverRevisionRepository::class);
$revisionRepo->method('getLatestRevisionNumber')->willReturn(3);
$revisionRepo->expects($this->once())
->method('insert')
->with($this->callback(function (array $data): bool {
return $data['revision'] === 4;
}))
->willReturn(100);
$service = $this->createService($revisionRepo);
$result = $service->createRevision(self::TENANT_ID, self::HANDOVER_ID, ['name' => 'Test'], 'draft', 1, self::USER_ID);
$this->assertSame(100, $result);
}
public function testCreateRevisionStoresSnapshot(): void
{
$revisionRepo = $this->createMock(HandoverRevisionRepository::class);
$revisionRepo->method('getLatestRevisionNumber')->willReturn(0);
$revisionRepo->expects($this->once())
->method('insert')
->with($this->callback(function (array $data): bool {
return $data['status'] === 'in_progress'
&& $data['schema_version'] === 2
&& $data['change_type'] === 'fields'
&& json_decode($data['field_values'], true) === ['name' => 'Acme'];
}))
->willReturn(1);
$service = $this->createService($revisionRepo);
$service->createRevision(self::TENANT_ID, self::HANDOVER_ID, ['name' => 'Acme'], 'in_progress', 2, self::USER_ID);
}
// ── computeDiff ──────────────────────────────────────────────────
public function testComputeDiffDetectsAddedFields(): void
{
$service = $this->createService();
$diff = $service->computeDiff([], ['name' => 'New']);
$this->assertArrayHasKey('name', $diff);
$this->assertSame('added', $diff['name']['type']);
$this->assertNull($diff['name']['old']);
$this->assertSame('New', $diff['name']['new']);
}
public function testComputeDiffDetectsRemovedFields(): void
{
$service = $this->createService();
$diff = $service->computeDiff(['name' => 'Old'], []);
$this->assertArrayHasKey('name', $diff);
$this->assertSame('removed', $diff['name']['type']);
$this->assertSame('Old', $diff['name']['old']);
$this->assertNull($diff['name']['new']);
}
public function testComputeDiffDetectsChangedFields(): void
{
$service = $this->createService();
$diff = $service->computeDiff(['name' => 'Old', 'priority' => 'low'], ['name' => 'New', 'priority' => 'low']);
$this->assertArrayHasKey('name', $diff);
$this->assertSame('changed', $diff['name']['type']);
$this->assertSame('Old', $diff['name']['old']);
$this->assertSame('New', $diff['name']['new']);
$this->assertArrayNotHasKey('priority', $diff);
}
public function testComputeDiffHandlesEmptyArrays(): void
{
$service = $this->createService();
$diff = $service->computeDiff([], []);
$this->assertSame([], $diff);
}
// ── restoreRevision ──────────────────────────────────────────────
public function testRestoreRevisionRequiresManagePermission(): void
{
$service = $this->createService();
$result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_CREATE);
$this->assertFalse($result['ok']);
$this->assertArrayHasKey('general', $result['errors']);
}
public function testRestoreRevisionRejectsInvalidRevision(): void
{
$revisionRepo = $this->createMock(HandoverRevisionRepository::class);
$revisionRepo->method('findByRevision')->willReturn(null);
$service = $this->createService($revisionRepo);
$result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 999, self::USER_ID, HandoverService::PERMISSION_MANAGE);
$this->assertFalse($result['ok']);
}
public function testRestoreRevisionCreatesNewRevision(): void
{
$revisionRepo = $this->createMock(HandoverRevisionRepository::class);
$revisionRepo->method('findByRevision')->willReturn([
'id' => 1,
'revision' => 2,
'field_values' => '{"name":"Old value"}',
'status' => 'draft',
]);
$revisionRepo->method('getLatestRevisionNumber')->willReturn(5);
$revisionRepo->expects($this->once())
->method('insert')
->with($this->callback(function (array $data): bool {
return $data['revision'] === 6
&& $data['change_type'] === 'restore'
&& json_decode($data['field_values'], true) === ['name' => 'Old value'];
}))
->willReturn(200);
$handoverRepo = $this->createMock(HandoverRepository::class);
$handoverRepo->method('findById')->willReturn([
'id' => self::HANDOVER_ID,
'status' => 'in_progress',
'field_values' => '{"name":"Current value"}',
'schema_snapshot' => '{"version":1,"fields":[]}',
]);
$handoverRepo->expects($this->once())->method('beginTransaction');
$handoverRepo->expects($this->once())->method('commitTransaction');
$handoverRepo->expects($this->never())->method('rollbackTransaction');
$handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(true);
$handoverRepo->expects($this->once())->method('updateStatus')->willReturn(true);
$service = $this->createService($revisionRepo, $handoverRepo);
$result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 2, self::USER_ID, HandoverService::PERMISSION_MANAGE);
$this->assertTrue($result['ok']);
}
public function testRestoreRevisionSkipsStatusUpdateWhenSame(): void
{
$revisionRepo = $this->createMock(HandoverRevisionRepository::class);
$revisionRepo->method('findByRevision')->willReturn([
'id' => 1,
'revision' => 1,
'field_values' => '{"name":"Old"}',
'status' => 'draft',
]);
$revisionRepo->method('getLatestRevisionNumber')->willReturn(3);
$revisionRepo->method('insert')->willReturn(100);
$handoverRepo = $this->createMock(HandoverRepository::class);
$handoverRepo->method('findById')->willReturn([
'id' => self::HANDOVER_ID,
'status' => 'draft',
'field_values' => '{"name":"Current"}',
'schema_snapshot' => '{"version":1,"fields":[]}',
]);
$handoverRepo->expects($this->once())->method('beginTransaction');
$handoverRepo->expects($this->once())->method('commitTransaction');
$handoverRepo->expects($this->never())->method('rollbackTransaction');
$handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(true);
$handoverRepo->expects($this->never())->method('updateStatus');
$service = $this->createService($revisionRepo, $handoverRepo);
$result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_MANAGE);
$this->assertTrue($result['ok']);
}
public function testRestoreRevisionRollsBackWhenFieldUpdateFails(): void
{
$revisionRepo = $this->createMock(HandoverRevisionRepository::class);
$revisionRepo->method('findByRevision')->willReturn([
'id' => 1,
'revision' => 1,
'field_values' => '{"name":"Old"}',
'status' => 'draft',
]);
$revisionRepo->expects($this->never())->method('insert');
$handoverRepo = $this->createMock(HandoverRepository::class);
$handoverRepo->method('findById')->willReturn([
'id' => self::HANDOVER_ID,
'status' => 'draft',
'field_values' => '{"name":"Current"}',
'schema_snapshot' => '{"version":1,"fields":[]}',
]);
$handoverRepo->expects($this->once())->method('beginTransaction');
$handoverRepo->expects($this->once())->method('rollbackTransaction');
$handoverRepo->expects($this->never())->method('commitTransaction');
$handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(false);
$handoverRepo->expects($this->never())->method('updateStatus');
$service = $this->createService($revisionRepo, $handoverRepo);
$result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_MANAGE);
$this->assertFalse($result['ok']);
$this->assertArrayHasKey('general', $result['errors']);
}
public function testRestoreRevisionRollsBackWhenRevisionInsertFails(): void
{
$revisionRepo = $this->createMock(HandoverRevisionRepository::class);
$revisionRepo->method('findByRevision')->willReturn([
'id' => 1,
'revision' => 1,
'field_values' => '{"name":"Old"}',
'status' => 'draft',
]);
$revisionRepo->method('getLatestRevisionNumber')->willReturn(3);
$revisionRepo->expects($this->once())->method('insert')->willReturn(null);
$handoverRepo = $this->createMock(HandoverRepository::class);
$handoverRepo->method('findById')->willReturn([
'id' => self::HANDOVER_ID,
'status' => 'draft',
'field_values' => '{"name":"Current"}',
'schema_snapshot' => '{"version":1,"fields":[]}',
]);
$handoverRepo->expects($this->once())->method('beginTransaction');
$handoverRepo->expects($this->once())->method('rollbackTransaction');
$handoverRepo->expects($this->never())->method('commitTransaction');
$handoverRepo->expects($this->once())->method('updateFieldValues')->willReturn(true);
$handoverRepo->expects($this->never())->method('updateStatus');
$service = $this->createService($revisionRepo, $handoverRepo);
$result = $service->restoreRevision(self::TENANT_ID, self::HANDOVER_ID, 1, self::USER_ID, HandoverService::PERMISSION_MANAGE);
$this->assertFalse($result['ok']);
}
}