CodebergSyncService berechnet am Ende jedes Sync-Laufs (periodisch und webhook-getriggert, auch bei PR-Aenderungen) die Summe der offenen Pull Requests aller konfigurierten Projekte und speichert sie als KPI "open_prs" im bestehenden generischen KPI-Repository - sie erscheint damit automatisch im KPI-Monitor der Wand und im KPI-Modal. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
160 lines
5.1 KiB
PHP
160 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Monitor\Api\Tests\Controller;
|
|
|
|
use Monitor\Api\Controller\TicketController;
|
|
use Monitor\Api\Db\Database;
|
|
use Monitor\Api\Http\NotFoundException;
|
|
use Monitor\Api\Http\Request;
|
|
use Monitor\Api\Repository\KpiRepository;
|
|
use Monitor\Api\Repository\RepoConfigRepository;
|
|
use Monitor\Api\Repository\TicketRepository;
|
|
use Monitor\Api\Sync\CodebergClient;
|
|
use Monitor\Api\Sync\CodebergSyncService;
|
|
use PHPUnit\Framework\TestCase;
|
|
use RuntimeException;
|
|
|
|
/** Testdouble fuer die Resync-Tests unten - liefert nie echte Issues, kann aber auf Wunsch fehlschlagen. */
|
|
final class StubCodebergClient implements CodebergClient
|
|
{
|
|
public function __construct(private readonly bool $shouldFail = false)
|
|
{
|
|
}
|
|
|
|
public function listIssues(string $owner, string $repo, string $state): array
|
|
{
|
|
if ($this->shouldFail) {
|
|
throw new RuntimeException('Codeberg-API nicht erreichbar.');
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
public function repoPermissions(string $owner, string $repo): array
|
|
{
|
|
return ['push' => true];
|
|
}
|
|
|
|
public function listWebhooks(string $owner, string $repo): array
|
|
{
|
|
return [];
|
|
}
|
|
|
|
public function countOpenPullRequests(string $owner, string $repo): int
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
final class TicketControllerTest extends TestCase
|
|
{
|
|
private TicketController $controller;
|
|
private TicketRepository $repository;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->repository = new TicketRepository(Database::inMemory());
|
|
$this->controller = new TicketController(
|
|
$this->repository,
|
|
new CodebergSyncService(
|
|
new StubCodebergClient(),
|
|
$this->repository,
|
|
new RepoConfigRepository(Database::inMemory()),
|
|
new KpiRepository(Database::inMemory())
|
|
)
|
|
);
|
|
}
|
|
|
|
public function testIndexReturnsAllTickets(): void
|
|
{
|
|
$this->repository->create(['title' => 'A']);
|
|
|
|
$response = $this->controller->index(Request::create('GET', '/api/v1/tickets'));
|
|
|
|
self::assertSame(200, $response->status);
|
|
self::assertCount(1, $response->body()['tickets']);
|
|
}
|
|
|
|
public function testCreateRejectsMissingTitle(): void
|
|
{
|
|
$response = $this->controller->create(Request::create('POST', '/api/v1/tickets', body: []));
|
|
|
|
self::assertSame(422, $response->status);
|
|
}
|
|
|
|
public function testCreateReturns201WithTicket(): void
|
|
{
|
|
$response = $this->controller->create(Request::create('POST', '/api/v1/tickets', body: ['title' => 'Neu']));
|
|
|
|
self::assertSame(201, $response->status);
|
|
self::assertSame('Neu', $response->body()['ticket']['title']);
|
|
}
|
|
|
|
public function testCreateRejectsInvalidColumnWith422(): void
|
|
{
|
|
$response = $this->controller->create(Request::create('POST', '/api/v1/tickets', body: ['title' => 'X', 'column' => 'invalid']));
|
|
|
|
self::assertSame(422, $response->status);
|
|
}
|
|
|
|
public function testUpdateMovesTicket(): void
|
|
{
|
|
$ticket = $this->repository->create(['title' => 'X']);
|
|
|
|
$response = $this->controller->update(
|
|
Request::create('PATCH', '/api/v1/tickets/' . $ticket['id'], body: ['column' => 'bereit']),
|
|
['id' => (string) $ticket['id']]
|
|
);
|
|
|
|
self::assertSame(200, $response->status);
|
|
self::assertSame('bereit', $response->body()['ticket']['column_name']);
|
|
}
|
|
|
|
public function testUpdatePropagatesNotFoundForUnknownTicket(): void
|
|
{
|
|
$this->expectException(NotFoundException::class);
|
|
|
|
$this->controller->update(
|
|
Request::create('PATCH', '/api/v1/tickets/999', body: ['column' => 'bereit']),
|
|
['id' => '999']
|
|
);
|
|
}
|
|
|
|
public function testResyncRemovesOrphanedTicketOfConfiguredRepoAndReturnsNewBoardState(): void
|
|
{
|
|
// 'fds2610/monitor' ist eines der per Migration seedenden Default-Projekte (siehe
|
|
// 003_repo_configs.sql) - der StubCodebergClient liefert dafuer keine Issues, das
|
|
// Ticket ist also eine Karteileiche im Sinne von Issue #51.
|
|
$this->repository->create([
|
|
'title' => 'Karteileiche',
|
|
'repo' => 'fds2610/monitor',
|
|
'external_ref' => '1',
|
|
'source' => 'codeberg',
|
|
]);
|
|
|
|
$response = $this->controller->resync(Request::create('POST', '/api/v1/tickets/resync'));
|
|
|
|
self::assertSame(200, $response->status);
|
|
self::assertSame([], $response->body()['tickets']);
|
|
}
|
|
|
|
public function testResyncReturns502WhenCodebergUnreachable(): void
|
|
{
|
|
$repository = new TicketRepository(Database::inMemory());
|
|
$controller = new TicketController(
|
|
$repository,
|
|
new CodebergSyncService(
|
|
new StubCodebergClient(shouldFail: true),
|
|
$repository,
|
|
new RepoConfigRepository(Database::inMemory()),
|
|
new KpiRepository(Database::inMemory())
|
|
)
|
|
);
|
|
|
|
$response = $controller->resync(Request::create('POST', '/api/v1/tickets/resync'));
|
|
|
|
self::assertSame(502, $response->status);
|
|
}
|
|
}
|