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.
150 lines
5.0 KiB
PHP
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);
|
|
}
|
|
}
|