forked from fa/breadcrumb-the-shire
350 lines
12 KiB
PHP
350 lines
12 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace MintyPHP\Tests\Module\Helpdesk\Service;
|
||
|
|
|
||
|
|
use MintyPHP\Module\Helpdesk\Repository\UpdateRepository;
|
||
|
|
use MintyPHP\Module\Helpdesk\Service\BcODataGateway;
|
||
|
|
use MintyPHP\Module\Helpdesk\Service\UpdateService;
|
||
|
|
use PHPUnit\Framework\TestCase;
|
||
|
|
|
||
|
|
class UpdateServiceTest extends TestCase
|
||
|
|
{
|
||
|
|
private const TENANT_ID = 1;
|
||
|
|
private const USER_ID = 42;
|
||
|
|
|
||
|
|
private function createService(
|
||
|
|
?UpdateRepository $repository = null,
|
||
|
|
?BcODataGateway $bcGateway = null,
|
||
|
|
): UpdateService {
|
||
|
|
$repository = $repository ?? $this->createMock(UpdateRepository::class);
|
||
|
|
$bcGateway = $bcGateway ?? $this->createMock(BcODataGateway::class);
|
||
|
|
|
||
|
|
return new UpdateService($repository, $bcGateway);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function makeBcTicket(string $no = 'TK-001', string $category = 'UPDATE', string $customerNo = 'D10001', string $customerName = 'Acme Corp'): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'No' => $no,
|
||
|
|
'Customer_No' => $customerNo,
|
||
|
|
'Cust_Name' => $customerName,
|
||
|
|
'Category_1_Code' => $category,
|
||
|
|
'Ticket_State' => 'Offen',
|
||
|
|
'Created_On' => '2026-04-01T10:00:00Z',
|
||
|
|
'Last_Activity_Date' => '2026-04-15T14:30:00Z',
|
||
|
|
'Support_User_Name' => 'Max Mustermann',
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Assign tests ─────────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testAssignUpdateHappyPath(): void
|
||
|
|
{
|
||
|
|
$repo = $this->createMock(UpdateRepository::class);
|
||
|
|
$repo->method('findByTicketNo')->willReturn(null);
|
||
|
|
$repo->expects($this->once())->method('insert')->willReturn(1);
|
||
|
|
|
||
|
|
$service = $this->createService($repo);
|
||
|
|
$result = $service->assignUpdate(
|
||
|
|
self::TENANT_ID,
|
||
|
|
'TK-001',
|
||
|
|
'D10001',
|
||
|
|
'Acme Corp',
|
||
|
|
'UPDATE',
|
||
|
|
'DNS00001',
|
||
|
|
'example.com',
|
||
|
|
'2026-02-mysyde',
|
||
|
|
'Initial update',
|
||
|
|
self::USER_ID
|
||
|
|
);
|
||
|
|
|
||
|
|
$this->assertTrue($result['ok']);
|
||
|
|
$this->assertSame(1, $result['id']);
|
||
|
|
$this->assertEmpty($result['errors']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testAssignUpdateUpdatesExistingRecord(): void
|
||
|
|
{
|
||
|
|
$repo = $this->createMock(UpdateRepository::class);
|
||
|
|
$repo->method('findByTicketNo')->willReturn([
|
||
|
|
'id' => 5,
|
||
|
|
'ticket_no' => 'TK-001',
|
||
|
|
'status' => 'open',
|
||
|
|
]);
|
||
|
|
$repo->expects($this->once())->method('updateAssignment')->willReturn(true);
|
||
|
|
$repo->expects($this->never())->method('insert');
|
||
|
|
|
||
|
|
$service = $this->createService($repo);
|
||
|
|
$result = $service->assignUpdate(
|
||
|
|
self::TENANT_ID,
|
||
|
|
'TK-001',
|
||
|
|
'D10001',
|
||
|
|
'Acme Corp',
|
||
|
|
'UPDATE',
|
||
|
|
'DNS00002',
|
||
|
|
'new.example.com',
|
||
|
|
'2026-03-update',
|
||
|
|
'Changed domain',
|
||
|
|
self::USER_ID
|
||
|
|
);
|
||
|
|
|
||
|
|
$this->assertTrue($result['ok']);
|
||
|
|
$this->assertSame(5, $result['id']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testAssignUpdateRejectsEmptyTicketNo(): void
|
||
|
|
{
|
||
|
|
$service = $this->createService();
|
||
|
|
$result = $service->assignUpdate(
|
||
|
|
self::TENANT_ID,
|
||
|
|
'',
|
||
|
|
'D10001',
|
||
|
|
'Acme Corp',
|
||
|
|
'UPDATE',
|
||
|
|
'DNS00001',
|
||
|
|
'example.com',
|
||
|
|
'',
|
||
|
|
'',
|
||
|
|
self::USER_ID
|
||
|
|
);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertArrayHasKey('ticket_no', $result['errors']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testAssignUpdateRejectsEmptyDebitorNo(): void
|
||
|
|
{
|
||
|
|
$service = $this->createService();
|
||
|
|
$result = $service->assignUpdate(
|
||
|
|
self::TENANT_ID,
|
||
|
|
'TK-001',
|
||
|
|
'',
|
||
|
|
'Acme Corp',
|
||
|
|
'UPDATE',
|
||
|
|
'DNS00001',
|
||
|
|
'example.com',
|
||
|
|
'',
|
||
|
|
'',
|
||
|
|
self::USER_ID
|
||
|
|
);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertArrayHasKey('debitor_no', $result['errors']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testAssignUpdateRejectsInvalidCategory(): void
|
||
|
|
{
|
||
|
|
$service = $this->createService();
|
||
|
|
$result = $service->assignUpdate(
|
||
|
|
self::TENANT_ID,
|
||
|
|
'TK-001',
|
||
|
|
'D10001',
|
||
|
|
'Acme Corp',
|
||
|
|
'BUGFIX',
|
||
|
|
'DNS00001',
|
||
|
|
'example.com',
|
||
|
|
'',
|
||
|
|
'',
|
||
|
|
self::USER_ID
|
||
|
|
);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertArrayHasKey('category_code', $result['errors']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testAssignUpdateSetsStatusAssignedWhenDomainProvided(): void
|
||
|
|
{
|
||
|
|
$repo = $this->createMock(UpdateRepository::class);
|
||
|
|
$repo->method('findByTicketNo')->willReturn(null);
|
||
|
|
$repo->expects($this->once())
|
||
|
|
->method('insert')
|
||
|
|
->with($this->callback(function (array $data): bool {
|
||
|
|
return $data['status'] === 'assigned' && $data['domain_no'] === 'DNS00001';
|
||
|
|
}))
|
||
|
|
->willReturn(1);
|
||
|
|
|
||
|
|
$service = $this->createService($repo);
|
||
|
|
$service->assignUpdate(
|
||
|
|
self::TENANT_ID,
|
||
|
|
'TK-001',
|
||
|
|
'D10001',
|
||
|
|
'Acme Corp',
|
||
|
|
'UPDATE',
|
||
|
|
'DNS00001',
|
||
|
|
'example.com',
|
||
|
|
'',
|
||
|
|
'',
|
||
|
|
self::USER_ID
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testAssignUpdateSetsStatusOpenWhenNoDomainOrGitea(): void
|
||
|
|
{
|
||
|
|
$repo = $this->createMock(UpdateRepository::class);
|
||
|
|
$repo->method('findByTicketNo')->willReturn(null);
|
||
|
|
$repo->expects($this->once())
|
||
|
|
->method('insert')
|
||
|
|
->with($this->callback(function (array $data): bool {
|
||
|
|
return $data['status'] === 'open' && $data['domain_no'] === '' && $data['gitea_path'] === '';
|
||
|
|
}))
|
||
|
|
->willReturn(1);
|
||
|
|
|
||
|
|
$service = $this->createService($repo);
|
||
|
|
$service->assignUpdate(
|
||
|
|
self::TENANT_ID,
|
||
|
|
'TK-001',
|
||
|
|
'D10001',
|
||
|
|
'Acme Corp',
|
||
|
|
'UPDATE',
|
||
|
|
'',
|
||
|
|
'',
|
||
|
|
'',
|
||
|
|
'',
|
||
|
|
self::USER_ID
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testAssignUpdateHandlesInsertFailure(): void
|
||
|
|
{
|
||
|
|
$repo = $this->createMock(UpdateRepository::class);
|
||
|
|
$repo->method('findByTicketNo')->willReturn(null);
|
||
|
|
$repo->method('insert')->willReturn(null);
|
||
|
|
|
||
|
|
$service = $this->createService($repo);
|
||
|
|
$result = $service->assignUpdate(
|
||
|
|
self::TENANT_ID,
|
||
|
|
'TK-001',
|
||
|
|
'D10001',
|
||
|
|
'Acme Corp',
|
||
|
|
'UPDATE',
|
||
|
|
'DNS00001',
|
||
|
|
'example.com',
|
||
|
|
'',
|
||
|
|
'',
|
||
|
|
self::USER_ID
|
||
|
|
);
|
||
|
|
|
||
|
|
$this->assertFalse($result['ok']);
|
||
|
|
$this->assertArrayHasKey('general', $result['errors']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Merge list tests ─────────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testGetUpdatesListMergesBcWithLocalData(): void
|
||
|
|
{
|
||
|
|
$bcGateway = $this->createMock(BcODataGateway::class);
|
||
|
|
$bcGateway->method('fetchUpdateTickets')->willReturn([
|
||
|
|
'tickets' => [
|
||
|
|
$this->makeBcTicket('TK-001', 'UPDATE'),
|
||
|
|
$this->makeBcTicket('TK-002', 'UPDATE-HF'),
|
||
|
|
$this->makeBcTicket('TK-003', 'UPDATE'),
|
||
|
|
],
|
||
|
|
'truncated' => false,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$repo = $this->createMock(UpdateRepository::class);
|
||
|
|
$repo->method('findAllByTenant')->willReturn([
|
||
|
|
['ticket_no' => 'TK-001', 'domain_no' => 'DNS00001', 'domain_url' => 'example.com', 'gitea_path' => '2026-02-mysyde', 'notes' => '', 'status' => 'assigned', 'id' => 1],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$service = $this->createService($repo, $bcGateway);
|
||
|
|
$result = $service->getUpdatesList(self::TENANT_ID);
|
||
|
|
|
||
|
|
$this->assertCount(3, $result['rows']);
|
||
|
|
|
||
|
|
// TK-001 should be enriched with local data
|
||
|
|
$tk001 = $result['rows'][0];
|
||
|
|
$this->assertSame('TK-001', $tk001['ticket_no']);
|
||
|
|
$this->assertSame('assigned', $tk001['status']);
|
||
|
|
$this->assertSame('DNS00001', $tk001['domain_no']);
|
||
|
|
$this->assertSame('2026-02-mysyde', $tk001['gitea_path']);
|
||
|
|
|
||
|
|
// TK-002 should be open (no local record)
|
||
|
|
$tk002 = $result['rows'][1];
|
||
|
|
$this->assertSame('TK-002', $tk002['ticket_no']);
|
||
|
|
$this->assertSame('open', $tk002['status']);
|
||
|
|
$this->assertSame('', $tk002['domain_no']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testGetUpdatesListFiltersByStatus(): void
|
||
|
|
{
|
||
|
|
$bcGateway = $this->createMock(BcODataGateway::class);
|
||
|
|
$bcGateway->method('fetchUpdateTickets')->willReturn([
|
||
|
|
'tickets' => [
|
||
|
|
$this->makeBcTicket('TK-001'),
|
||
|
|
$this->makeBcTicket('TK-002'),
|
||
|
|
],
|
||
|
|
'truncated' => false,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$repo = $this->createMock(UpdateRepository::class);
|
||
|
|
$repo->method('findAllByTenant')->willReturn([
|
||
|
|
['ticket_no' => 'TK-001', 'domain_no' => 'DNS00001', 'domain_url' => '', 'gitea_path' => '', 'notes' => '', 'status' => 'assigned', 'id' => 1],
|
||
|
|
]);
|
||
|
|
|
||
|
|
$service = $this->createService($repo, $bcGateway);
|
||
|
|
$result = $service->getUpdatesList(self::TENANT_ID, ['status' => 'open']);
|
||
|
|
|
||
|
|
$this->assertCount(1, $result['rows']);
|
||
|
|
$this->assertSame('TK-002', $result['rows'][0]['ticket_no']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testGetUpdatesListFiltersByCategory(): void
|
||
|
|
{
|
||
|
|
$bcGateway = $this->createMock(BcODataGateway::class);
|
||
|
|
$bcGateway->method('fetchUpdateTickets')->willReturn([
|
||
|
|
'tickets' => [
|
||
|
|
$this->makeBcTicket('TK-001', 'UPDATE'),
|
||
|
|
$this->makeBcTicket('TK-002', 'UPDATE-HF'),
|
||
|
|
],
|
||
|
|
'truncated' => false,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$repo = $this->createMock(UpdateRepository::class);
|
||
|
|
$repo->method('findAllByTenant')->willReturn([]);
|
||
|
|
|
||
|
|
$service = $this->createService($repo, $bcGateway);
|
||
|
|
$result = $service->getUpdatesList(self::TENANT_ID, ['category' => 'UPDATE-HF']);
|
||
|
|
|
||
|
|
$this->assertCount(1, $result['rows']);
|
||
|
|
$this->assertSame('TK-002', $result['rows'][0]['ticket_no']);
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Static helper tests ──────────────────────────────────────────
|
||
|
|
|
||
|
|
public function testStatusLabelsReturnExpectedValues(): void
|
||
|
|
{
|
||
|
|
$this->assertNotEmpty(UpdateService::statusLabel('open'));
|
||
|
|
$this->assertNotEmpty(UpdateService::statusLabel('assigned'));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testCategoryLabelsReturnExpectedValues(): void
|
||
|
|
{
|
||
|
|
$this->assertNotEmpty(UpdateService::categoryLabel('UPDATE'));
|
||
|
|
$this->assertNotEmpty(UpdateService::categoryLabel('UPDATE-HF'));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testStatusVariantsReturnValidValues(): void
|
||
|
|
{
|
||
|
|
$validVariants = ['neutral', 'info', 'warning', 'success', 'error'];
|
||
|
|
$this->assertContains(UpdateService::statusVariant('open'), $validVariants);
|
||
|
|
$this->assertContains(UpdateService::statusVariant('assigned'), $validVariants);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testTicketStateLabelsReturnExpectedValues(): void
|
||
|
|
{
|
||
|
|
$this->assertNotEmpty(UpdateService::ticketStateLabel('Offen'));
|
||
|
|
$this->assertNotEmpty(UpdateService::ticketStateLabel('In Bearbeitung'));
|
||
|
|
$this->assertNotEmpty(UpdateService::ticketStateLabel('Erledigt'));
|
||
|
|
$this->assertNotEmpty(UpdateService::ticketStateLabel('Geschlossen'));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testTicketStateVariantsReturnValidValues(): void
|
||
|
|
{
|
||
|
|
$validVariants = ['neutral', 'info', 'warning', 'success', 'error'];
|
||
|
|
$this->assertContains(UpdateService::ticketStateVariant('Offen'), $validVariants);
|
||
|
|
$this->assertContains(UpdateService::ticketStateVariant('Erledigt'), $validVariants);
|
||
|
|
$this->assertContains(UpdateService::ticketStateVariant('Geschlossen'), $validVariants);
|
||
|
|
}
|
||
|
|
}
|