Manchmal werden bei Codeberg geloeschte oder anderweitig verschwundene Issues vom periodischen Sync nicht bereinigt (er upsertet nur, ruehrt fehlende Issues nie an). Neuer Knopf neben der "Kanban-Board"-Ueberschrift stoesst dafuer einen synchronen Voll-Resync an (POST /api/v1/tickets/resync, X-Admin-Key): jedes konfigurierte Projekt wird neu gelesen, Tickets zu inzwischen verschwundenen Issues werden je Projekt entfernt (auch wenn sie manuell auf eine nicht vom Sync verwaltete Spalte wie "fertig" bewegt wurden), Tickets zu weiterhin existierenden Issues bleiben unangetastet. Der PO geht dabei sichtbar ans Board und steht dort, bis die Anfrage abgeschlossen ist - anders als beim automatischen Board-Besuch (feste Animationsdauer je Stage) macht das die tatsaechliche Sync-Dauer sichtbar. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
134 lines
4.5 KiB
PHP
134 lines
4.5 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\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 [];
|
|
}
|
|
}
|
|
|
|
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()))
|
|
);
|
|
}
|
|
|
|
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()))
|
|
);
|
|
|
|
$response = $controller->resync(Request::create('POST', '/api/v1/tickets/resync'));
|
|
|
|
self::assertSame(502, $response->status);
|
|
}
|
|
}
|