Files
git-contribution-graph/tests/Unit/Command/RefetchContributionsCommandTest.php
T
haylan 4ff45b7c46 feat(store): fetch contributions incrementally and persist history in SQLite
Bound each provider's startFetch()/resolveFetch() to an optional
since/until window, wire a SQLite-backed ContributionStore into
ContributionAggregator (fetch only the trailing window past the last
stored date, merge into the store, prune by CONTRIBUTIONS_RETENTION_DAYS),
and add a graph:contributions:refetch command to force a full or ranged
re-fetch in <=365-day chunks. This is the root fix for the full
365-day-refetch timeout that used to hit on every cache miss.
2026-07-12 23:42:41 +02:00

150 lines
5.0 KiB
PHP

<?php
declare(strict_types=1);
namespace GitContributionGraph\Tests\Unit\Command;
use GitContributionGraph\Command\RefetchContributionsCommand;
use GitContributionGraph\Service\ContributionStore;
use GitContributionGraph\Service\Provider\ProviderInterface;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
#[CoversClass(RefetchContributionsCommand::class)]
final class RefetchContributionsCommandTest extends TestCase
{
/** @param ?array<string, int> $fresh */
private function makeProvider(
string $name,
bool $configured = true,
?array $fresh = null,
?\Throwable $throws = null,
): ProviderInterface {
$provider = $this->createStub(ProviderInterface::class);
$provider->method('getName')->willReturn($name);
$provider->method('isConfigured')->willReturn($configured);
if ($throws !== null) {
$provider->method('startFetch')->willThrowException($throws);
} elseif ($fresh !== null) {
$provider->method('resolveFetch')->willReturn($fresh);
}
return $provider;
}
/** @param iterable<ProviderInterface> $providers */
private function makeTester(iterable $providers, ContributionStore $store): CommandTester
{
$command = new RefetchContributionsCommand($providers, $store);
$application = new Application();
$application->add($command);
return new CommandTester($application->find('graph:contributions:refetch'));
}
#[Test]
public function it_refetches_the_default_365_day_window_for_all_configured_providers(): void
{
$store = new ContributionStore(':memory:');
$provider = $this->makeProvider('github', fresh: ['2024-06-10' => 3]);
$tester = $this->makeTester([$provider], $store);
$exitCode = $tester->execute([]);
$this->assertSame(0, $exitCode);
$this->assertCount(1, $store->all('github'));
}
#[Test]
public function it_skips_unconfigured_providers_by_default(): void
{
$store = new ContributionStore(':memory:');
$provider = $this->makeProvider('github', configured: false);
$tester = $this->makeTester([$provider], $store);
$tester->execute([]);
$this->assertCount(0, $store->all('github'));
}
#[Test]
public function it_fails_cleanly_on_an_unknown_provider_name(): void
{
$store = new ContributionStore(':memory:');
$provider = $this->makeProvider('github');
$tester = $this->makeTester([$provider], $store);
$exitCode = $tester->execute(['--provider' => 'bogus']);
$this->assertSame(1, $exitCode);
$this->assertStringContainsString('Unknown provider: bogus', $tester->getDisplay());
}
#[Test]
public function it_restricts_to_the_named_provider(): void
{
$store = new ContributionStore(':memory:');
$github = $this->makeProvider('github', fresh: ['2024-06-10' => 1]);
$gitlab = $this->makeProvider('gitlab', fresh: ['2024-06-10' => 1]);
$tester = $this->makeTester([$github, $gitlab], $store);
$tester->execute(['--provider' => 'github']);
$this->assertCount(1, $store->all('github'));
$this->assertCount(0, $store->all('gitlab'));
}
#[Test]
public function it_continues_with_remaining_providers_when_one_throws(): void
{
$store = new ContributionStore(':memory:');
$failing = $this->makeProvider('gitlab', throws: new \RuntimeException('boom'));
$healthy = $this->makeProvider('github', fresh: ['2024-06-10' => 2]);
$tester = $this->makeTester([$failing, $healthy], $store);
$exitCode = $tester->execute([]);
$this->assertSame(0, $exitCode);
$this->assertCount(1, $store->all('github'));
$this->assertCount(0, $store->all('gitlab'));
}
#[Test]
public function it_fails_when_every_provider_errors(): void
{
$store = new ContributionStore(':memory:');
$failing = $this->makeProvider('github', throws: new \RuntimeException('boom'));
$tester = $this->makeTester([$failing], $store);
$exitCode = $tester->execute([]);
$this->assertSame(1, $exitCode);
}
#[Test]
public function it_splits_a_multi_year_range_into_365_day_chunks(): void
{
$store = new ContributionStore(':memory:');
$calls = 0;
$provider = $this->createStub(ProviderInterface::class);
$provider->method('getName')->willReturn('github');
$provider->method('isConfigured')->willReturn(true);
$provider->method('resolveFetch')->willReturnCallback(function () use (&$calls): array {
$calls++;
return [];
});
$tester = $this->makeTester([$provider], $store);
$tester->execute(['--from' => '2020-01-01', '--to' => '2023-01-01']);
$this->assertGreaterThan(1, $calls);
}
}