Merge pull request 'Neue KPI: offene PRs ueber alle Projekte (Issue #59)' (#60) from fds2610-bot/monitor:main into main
Reviewed-on: https://codeberg.org/fds2610/monitor/pulls/60
This commit is contained in:
commit
32f3a3d30c
11 changed files with 212 additions and 20 deletions
7
API.md
7
API.md
|
|
@ -293,6 +293,13 @@ siehe Issue #38.
|
|||
der Wert ueber 0 liegt - eine dauerhaft rote Kachel mit einer 0 waere genau das Rauschen, das den
|
||||
Ernstfall untergehen liesse.
|
||||
|
||||
Issue #59: die Kennzahl `open_prs` ("Offene PRs") wird nicht per `POST /api/v1/kpis/{key}`
|
||||
gemeldet, sondern am Ende jedes Sync-Laufs (`CodebergSyncService::sync()`/`resync()`) automatisch
|
||||
aus der Summe der offenen Pull Requests aller konfigurierten Projekte berechnet und ueber das
|
||||
generische KPI-Repository gespeichert. Ein Sync-Lauf startet u.a. bei jedem eingehenden
|
||||
Codeberg-Webhook (auch PR-Aenderungen) sowie periodisch, siehe
|
||||
[`POST /api/v1/webhooks/codeberg`](#post-apiv1webhookscodeberg--sofort-sync-per-webhook).
|
||||
|
||||
### `POST /api/v1/kpis/{key}` — KPI-Datenpunkt melden
|
||||
|
||||
Auth: `X-Api-Key`.
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
|
||||
use Monitor\Api\Config;
|
||||
use Monitor\Api\Db\Database;
|
||||
use Monitor\Api\Repository\KpiRepository;
|
||||
use Monitor\Api\Repository\RepoConfigRepository;
|
||||
use Monitor\Api\Repository\TicketRepository;
|
||||
use Monitor\Api\Sync\CodebergSyncService;
|
||||
|
|
@ -21,6 +22,7 @@ $service = new CodebergSyncService(
|
|||
new CurlCodebergClient($config->codebergApiBase(), $config->codebergToken()),
|
||||
new TicketRepository($db),
|
||||
new RepoConfigRepository($db),
|
||||
new KpiRepository($db),
|
||||
);
|
||||
|
||||
// Ohne --watch bleibt das Skript ein einmaliger Lauf (manueller Aufruf, siehe README).
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ final class Application
|
|||
// unabhaengig vom periodisch/webhook-getriebenen sync-Container (bin/sync-codeberg.php)
|
||||
// - beide teilen sich denselben CodebergSyncService, nur der Aufloeser ist verschieden.
|
||||
$codebergClient = new CurlCodebergClient($config->codebergApiBase(), $config->codebergToken());
|
||||
$syncService = new CodebergSyncService($codebergClient, $tickets, $repoConfigs);
|
||||
$syncService = new CodebergSyncService($codebergClient, $tickets, $repoConfigs, $kpis);
|
||||
$ticketController = new TicketController($tickets, $syncService);
|
||||
$eventController = new EventController($events);
|
||||
$systemController = new SystemController($systems, new LegacyStatusFileReader(), $config->statusFilePath());
|
||||
|
|
|
|||
|
|
@ -32,4 +32,10 @@ interface CodebergClient
|
|||
* @return array<int, array{active: bool, url: string, content_type: string}>
|
||||
*/
|
||||
public function listWebhooks(string $owner, string $repo): array;
|
||||
|
||||
/**
|
||||
* Issue #59: Anzahl offener Pull Requests eines Projekts, Grundlage fuer den
|
||||
* projektuebergreifenden "Offene PRs"-KPI (CodebergSyncService).
|
||||
*/
|
||||
public function countOpenPullRequests(string $owner, string $repo): int;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace Monitor\Api\Sync;
|
||||
|
||||
use Monitor\Api\Repository\KpiRepository;
|
||||
use Monitor\Api\Repository\RepoConfigRepository;
|
||||
use Monitor\Api\Repository\TicketRepository;
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ final class CodebergSyncService
|
|||
private readonly CodebergClient $client,
|
||||
private readonly TicketRepository $tickets,
|
||||
private readonly RepoConfigRepository $repoConfigs,
|
||||
private readonly KpiRepository $kpis,
|
||||
) {
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +55,8 @@ final class CodebergSyncService
|
|||
// oben nicht mehr angefasst und wuerden sonst dauerhaft in der alten, nicht mehr
|
||||
// zutreffenden Projektfarbe angezeigt.
|
||||
$this->tickets->clearColorForReposNotIn($this->repoSlugs($repoConfigs));
|
||||
|
||||
$this->reportOpenPullRequestKpi($repoConfigs);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -76,6 +80,26 @@ final class CodebergSyncService
|
|||
}
|
||||
|
||||
$this->tickets->clearColorForReposNotIn($this->repoSlugs($repoConfigs));
|
||||
|
||||
$this->reportOpenPullRequestKpi($repoConfigs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue #59: projektuebergreifende Summe offener PRs als KPI (F7). Laeuft am Ende von
|
||||
* sync()/resync() statt direkt im Webhook-Handler, weil dieser Payloads bewusst nicht
|
||||
* auswertet (siehe WebhookController) - ein PR-Webhook loest ohnehin denselben Sync-Lauf
|
||||
* aus wie jeder andere, der neue Wert ist danach also aktuell.
|
||||
*
|
||||
* @param array<int, array{owner: string, repo: string}> $repoConfigs
|
||||
*/
|
||||
private function reportOpenPullRequestKpi(array $repoConfigs): void
|
||||
{
|
||||
$openPrCount = array_sum(array_map(
|
||||
fn (array $repoConfig): int => $this->client->countOpenPullRequests($repoConfig['owner'], $repoConfig['repo']),
|
||||
$repoConfigs
|
||||
));
|
||||
|
||||
$this->kpis->report('open_prs', (float) $openPrCount, 'Offene PRs', null, null);
|
||||
}
|
||||
|
||||
/** @return string[] external_refs, die bei diesem Lauf tatsaechlich bei Codeberg gesehen wurden. */
|
||||
|
|
|
|||
|
|
@ -60,6 +60,31 @@ final class CurlCodebergClient implements CodebergClient
|
|||
return $issues;
|
||||
}
|
||||
|
||||
public function countOpenPullRequests(string $owner, string $repo): int
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
for ($page = 1; $page <= self::MAX_PAGES; $page++) {
|
||||
$url = sprintf(
|
||||
'%s/repos/%s/%s/issues?state=open&type=pulls&page=%d&limit=%d',
|
||||
$this->apiBase,
|
||||
rawurlencode($owner),
|
||||
rawurlencode($repo),
|
||||
$page,
|
||||
self::PER_PAGE
|
||||
);
|
||||
|
||||
$pagePulls = $this->fetch($url);
|
||||
$count += count($pagePulls);
|
||||
|
||||
if (count($pagePulls) < self::PER_PAGE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function repoPermissions(string $owner, string $repo): array
|
||||
{
|
||||
$url = sprintf('%s/repos/%s/%s', $this->apiBase, rawurlencode($owner), rawurlencode($repo));
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ final class RepoConfigControllerTest extends TestCase
|
|||
{
|
||||
return [['active' => true, 'url' => 'https://monitor.example.org/api/v1/webhooks/codeberg', 'content_type' => 'json']];
|
||||
}
|
||||
|
||||
public function countOpenPullRequests(string $owner, string $repo): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
$this->controller = new RepoConfigController(new RepoConfigRepository($db), $this->tickets, $healthChecker);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ 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;
|
||||
|
|
@ -30,6 +31,21 @@ final class StubCodebergClient implements CodebergClient
|
|||
|
||||
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
|
||||
|
|
@ -42,7 +58,12 @@ final class TicketControllerTest extends TestCase
|
|||
$this->repository = new TicketRepository(Database::inMemory());
|
||||
$this->controller = new TicketController(
|
||||
$this->repository,
|
||||
new CodebergSyncService(new StubCodebergClient(), $this->repository, new RepoConfigRepository(Database::inMemory()))
|
||||
new CodebergSyncService(
|
||||
new StubCodebergClient(),
|
||||
$this->repository,
|
||||
new RepoConfigRepository(Database::inMemory()),
|
||||
new KpiRepository(Database::inMemory())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -124,7 +145,12 @@ final class TicketControllerTest extends TestCase
|
|||
$repository = new TicketRepository(Database::inMemory());
|
||||
$controller = new TicketController(
|
||||
$repository,
|
||||
new CodebergSyncService(new StubCodebergClient(shouldFail: true), $repository, new RepoConfigRepository(Database::inMemory()))
|
||||
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'));
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||
namespace Monitor\Api\Tests\Sync;
|
||||
|
||||
use Monitor\Api\Db\Database;
|
||||
use Monitor\Api\Repository\KpiRepository;
|
||||
use Monitor\Api\Repository\RepoConfigRepository;
|
||||
use Monitor\Api\Repository\TicketRepository;
|
||||
use Monitor\Api\Sync\CodebergClient;
|
||||
|
|
@ -13,15 +14,35 @@ use PHPUnit\Framework\TestCase;
|
|||
|
||||
final class FakeCodebergClient implements CodebergClient
|
||||
{
|
||||
/** @param array<string, array<string, array<int, array{number:int,title:string,body:?string,labels:array}>>> $issuesByRepoAndState */
|
||||
public function __construct(private readonly array $issuesByRepoAndState)
|
||||
{
|
||||
/**
|
||||
* @param array<string, array<string, array<int, array{number:int,title:string,body:?string,labels:array}>>> $issuesByRepoAndState
|
||||
* @param array<string, int> $openPullRequestsByRepo
|
||||
*/
|
||||
public function __construct(
|
||||
private readonly array $issuesByRepoAndState,
|
||||
private readonly array $openPullRequestsByRepo = [],
|
||||
) {
|
||||
}
|
||||
|
||||
public function listIssues(string $owner, string $repo, string $state): array
|
||||
{
|
||||
return $this->issuesByRepoAndState[$owner . '/' . $repo][$state] ?? [];
|
||||
}
|
||||
|
||||
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 $this->openPullRequestsByRepo[$owner . '/' . $repo] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
final class CodebergSyncServiceTest extends TestCase
|
||||
|
|
@ -55,6 +76,12 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
return $repoConfigs;
|
||||
}
|
||||
|
||||
/** Frisches, unabhaengiges KpiRepository - der KPI-Wert selbst ist nicht Gegenstand der meisten Tests hier. */
|
||||
private static function kpis(): KpiRepository
|
||||
{
|
||||
return new KpiRepository(Database::inMemory());
|
||||
}
|
||||
|
||||
public function testSyncCreatesBacklogAndDoneTicketsColoredByProject(): void
|
||||
{
|
||||
$tickets = new TicketRepository(Database::inMemory());
|
||||
|
|
@ -67,7 +94,7 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
],
|
||||
]);
|
||||
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO])))->sync();
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]), self::kpis()))->sync();
|
||||
|
||||
$backlog = $tickets->all('backlog');
|
||||
$done = $tickets->all('done');
|
||||
|
|
@ -102,7 +129,7 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([
|
||||
self::MONITOR_REPO,
|
||||
['owner' => 'fds2610', 'repo' => 'Steuerkram', 'color' => '#28a745'],
|
||||
])))->sync();
|
||||
]), self::kpis()))->sync();
|
||||
|
||||
$backlog = $tickets->all('backlog');
|
||||
self::assertCount(2, $backlog, 'gleiche Issue-Nummer aus verschiedenen Projekten darf nicht zu einem Ticket verschmelzen');
|
||||
|
|
@ -125,14 +152,14 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
'closed' => [],
|
||||
]);
|
||||
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO])))->sync();
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]), self::kpis()))->sync();
|
||||
self::assertSame('#fbca04', $tickets->findByExternalRef('1', self::MONITOR_SLUG)['color']);
|
||||
|
||||
// Projekt wurde im Projekte-Modal entfernt (Issue #34): der naechste Sync-Lauf sieht
|
||||
// es nicht mehr in repoConfigsFor() und darf das verwaiste Ticket nicht laenger blau
|
||||
// faerben, obwohl es kein Projekt mit dieser Farbe mehr gibt.
|
||||
$client = new FakeCodebergClient([]);
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([])))->sync();
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([]), self::kpis()))->sync();
|
||||
|
||||
self::assertNull($tickets->findByExternalRef('1', self::MONITOR_SLUG)['color']);
|
||||
}
|
||||
|
|
@ -144,7 +171,7 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
'open' => [['number' => 1, 'title' => 'Issue', 'body' => null, 'labels' => []]],
|
||||
'closed' => [],
|
||||
]);
|
||||
$service = new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]));
|
||||
$service = new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]), self::kpis());
|
||||
|
||||
$service->sync();
|
||||
$ticket = $tickets->findByExternalRef('1', self::MONITOR_SLUG);
|
||||
|
|
@ -178,7 +205,7 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
'closed' => [],
|
||||
]);
|
||||
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO])))->sync();
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]), self::kpis()))->sync();
|
||||
|
||||
$ticket = $tickets->findByExternalRef('1', self::MONITOR_SLUG);
|
||||
self::assertSame($expectedColumn, $ticket['column_name']);
|
||||
|
|
@ -193,7 +220,7 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
],
|
||||
'closed' => [],
|
||||
]);
|
||||
$service = new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]));
|
||||
$service = new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]), self::kpis());
|
||||
|
||||
$service->sync();
|
||||
self::assertSame('bereit', $tickets->findByExternalRef('1', self::MONITOR_SLUG)['column_name']);
|
||||
|
|
@ -204,7 +231,7 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
],
|
||||
'closed' => [],
|
||||
]);
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO])))->sync();
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]), self::kpis()))->sync();
|
||||
|
||||
self::assertSame('in_arbeit', $tickets->findByExternalRef('1', self::MONITOR_SLUG)['column_name']);
|
||||
}
|
||||
|
|
@ -217,7 +244,7 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
'open' => [['number' => 1, 'title' => 'Issue', 'body' => null, 'labels' => []]],
|
||||
'closed' => [],
|
||||
]);
|
||||
(new CodebergSyncService($client, $tickets, $repoConfigs))->sync();
|
||||
(new CodebergSyncService($client, $tickets, $repoConfigs, self::kpis()))->sync();
|
||||
// Manuell auf "fertig" bewegt, damit sync() es normalerweise unangetastet liesse
|
||||
// (siehe testSyncDoesNotResetManuallyMovedTicket) - Issue #51 ist genau fuer solche
|
||||
// liegen gebliebenen Karteileichen gedacht, die der normale Sync nicht mehr anfasst.
|
||||
|
|
@ -225,7 +252,7 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
|
||||
// Issue 1 wurde bei Codeberg inzwischen komplett geloescht (taucht weder offen noch
|
||||
// geschlossen auf) - genau der Fall aus Issue #51, den der normale Sync nicht bereinigt.
|
||||
(new CodebergSyncService(new FakeCodebergClient([self::MONITOR_SLUG => ['open' => [], 'closed' => []]]), $tickets, $repoConfigs))->resync();
|
||||
(new CodebergSyncService(new FakeCodebergClient([self::MONITOR_SLUG => ['open' => [], 'closed' => []]]), $tickets, $repoConfigs, self::kpis()))->resync();
|
||||
|
||||
self::assertNull($tickets->findByExternalRef('1', self::MONITOR_SLUG));
|
||||
self::assertNull($tickets->find((int) $orphanTicket['id']));
|
||||
|
|
@ -239,13 +266,13 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
'open' => [['number' => 1, 'title' => 'Issue', 'body' => null, 'labels' => []]],
|
||||
'closed' => [],
|
||||
]);
|
||||
(new CodebergSyncService($client, $tickets, $repoConfigs))->sync();
|
||||
(new CodebergSyncService($client, $tickets, $repoConfigs, self::kpis()))->sync();
|
||||
$ticket = $tickets->findByExternalRef('1', self::MONITOR_SLUG);
|
||||
$tickets->update((int) $ticket['id'], ['column' => 'fertig']);
|
||||
|
||||
// Dasselbe Issue existiert bei Codeberg weiterhin (offen) - resync() darf es trotz
|
||||
// manuell gesetzter, nicht vom Sync verwalteter Spalte nicht als Karteileiche werfen.
|
||||
(new CodebergSyncService($client, $tickets, $repoConfigs))->resync();
|
||||
(new CodebergSyncService($client, $tickets, $repoConfigs, self::kpis()))->resync();
|
||||
|
||||
$stillFertig = $tickets->findByExternalRef('1', self::MONITOR_SLUG);
|
||||
self::assertNotNull($stillFertig);
|
||||
|
|
@ -261,7 +288,7 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
],
|
||||
'closed' => [],
|
||||
]);
|
||||
$service = new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]));
|
||||
$service = new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]), self::kpis());
|
||||
$service->sync();
|
||||
self::assertSame('testing', $tickets->findByExternalRef('1', self::MONITOR_SLUG)['column_name']);
|
||||
|
||||
|
|
@ -271,8 +298,43 @@ final class CodebergSyncServiceTest extends TestCase
|
|||
['number' => 1, 'title' => 'Issue', 'body' => null, 'labels' => [['name' => 'testing', 'color' => 'ff0000']]],
|
||||
],
|
||||
]);
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO])))->sync();
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([self::MONITOR_REPO]), self::kpis()))->sync();
|
||||
|
||||
self::assertSame('done', $tickets->findByExternalRef('1', self::MONITOR_SLUG)['column_name']);
|
||||
}
|
||||
|
||||
public function testSyncReportsOpenPullRequestCountAcrossProjectsAsKpi(): void
|
||||
{
|
||||
$tickets = new TicketRepository(Database::inMemory());
|
||||
$client = new FakeCodebergClient(
|
||||
issuesByRepoAndState: [],
|
||||
openPullRequestsByRepo: ['fds2610/monitor' => 2, 'fds2610/Steuerkram' => 3],
|
||||
);
|
||||
$kpis = self::kpis();
|
||||
|
||||
(new CodebergSyncService($client, $tickets, self::repoConfigsFor([
|
||||
self::MONITOR_REPO,
|
||||
['owner' => 'fds2610', 'repo' => 'Steuerkram', 'color' => '#28a745'],
|
||||
]), $kpis))->sync();
|
||||
|
||||
$kpi = $kpis->find('open_prs');
|
||||
self::assertNotNull($kpi);
|
||||
self::assertSame(5.0, $kpi['current_value']);
|
||||
self::assertSame('Offene PRs', $kpi['label']);
|
||||
}
|
||||
|
||||
public function testResyncAlsoRefreshesOpenPullRequestKpi(): void
|
||||
{
|
||||
$tickets = new TicketRepository(Database::inMemory());
|
||||
$repoConfigs = self::repoConfigsFor([self::MONITOR_REPO]);
|
||||
$kpis = self::kpis();
|
||||
$client = new FakeCodebergClient(
|
||||
issuesByRepoAndState: [self::MONITOR_SLUG => ['open' => [], 'closed' => []]],
|
||||
openPullRequestsByRepo: [self::MONITOR_SLUG => 4],
|
||||
);
|
||||
|
||||
(new CodebergSyncService($client, $tickets, $repoConfigs, $kpis))->resync();
|
||||
|
||||
self::assertSame(4.0, $kpis->find('open_prs')['current_value']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,5 +185,35 @@ namespace Monitor\Api\Tests\Sync {
|
|||
self::assertSame(403, $e->status);
|
||||
}
|
||||
}
|
||||
|
||||
public function testCountOpenPullRequestsCountsSinglePage(): void
|
||||
{
|
||||
CurlStub::$queue = [
|
||||
['status' => 200, 'body' => json_encode([
|
||||
['number' => 1, 'title' => 'PR A'],
|
||||
['number' => 2, 'title' => 'PR B'],
|
||||
])],
|
||||
];
|
||||
|
||||
$count = (new CurlCodebergClient('https://codeberg.org/api/v1'))->countOpenPullRequests('fds2610', 'monitor');
|
||||
|
||||
self::assertSame(2, $count);
|
||||
self::assertStringContainsString('state=open', CurlStub::$requestedUrls[0]);
|
||||
self::assertStringContainsString('type=pulls', CurlStub::$requestedUrls[0]);
|
||||
}
|
||||
|
||||
public function testCountOpenPullRequestsSumsAcrossPages(): void
|
||||
{
|
||||
$fullPage = array_fill(0, 50, ['number' => 1, 'title' => 'PR']);
|
||||
CurlStub::$queue = [
|
||||
['status' => 200, 'body' => json_encode($fullPage)],
|
||||
['status' => 200, 'body' => json_encode([['number' => 99, 'title' => 'Letzte PR']])],
|
||||
];
|
||||
|
||||
$count = (new CurlCodebergClient('https://codeberg.org/api/v1'))->countOpenPullRequests('fds2610', 'monitor');
|
||||
|
||||
self::assertSame(51, $count);
|
||||
self::assertCount(2, CurlStub::$requestedUrls);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,11 @@ final class RepoHealthCheckerTest extends TestCase
|
|||
|
||||
return $this->listWebhooks ?? [];
|
||||
}
|
||||
|
||||
public function countOpenPullRequests(string $owner, string $repo): int
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue