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.
159 lines
5.3 KiB
PHP
159 lines
5.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace GitContributionGraph\Tests\Unit\Service\Provider;
|
|
|
|
use GitContributionGraph\Service\Provider\GitHubProvider;
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Log\LoggerInterface;
|
|
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
|
use Symfony\Contracts\HttpClient\ResponseInterface;
|
|
|
|
#[CoversClass(GitHubProvider::class)]
|
|
final class GitHubProviderTest extends TestCase
|
|
{
|
|
private function makeProvider(
|
|
string $username = 'user',
|
|
string $token = 'token',
|
|
?HttpClientInterface $client = null,
|
|
): GitHubProvider {
|
|
return new GitHubProvider(
|
|
$client ?? $this->createStub(HttpClientInterface::class),
|
|
$username,
|
|
$token,
|
|
$this->createStub(LoggerInterface::class),
|
|
);
|
|
}
|
|
|
|
/** @param array<int, array{contributionDays: array<int, array{date: string, contributionCount: int}>}> $weeks */
|
|
private function stubGraphqlResponse(array $weeks): ResponseInterface
|
|
{
|
|
$response = $this->createStub(ResponseInterface::class);
|
|
$response->method('toArray')->willReturn([
|
|
'data' => [
|
|
'user' => [
|
|
'contributionsCollection' => [
|
|
'contributionCalendar' => ['weeks' => $weeks],
|
|
],
|
|
],
|
|
],
|
|
]);
|
|
|
|
return $response;
|
|
}
|
|
|
|
#[Test]
|
|
public function it_returns_github_as_name(): void
|
|
{
|
|
$this->assertSame('github', $this->makeProvider()->getName());
|
|
}
|
|
|
|
#[Test]
|
|
public function it_is_configured_when_credentials_are_set(): void
|
|
{
|
|
$this->assertTrue($this->makeProvider()->isConfigured());
|
|
}
|
|
|
|
#[Test]
|
|
public function it_is_not_configured_when_username_is_empty(): void
|
|
{
|
|
$this->assertFalse($this->makeProvider(username: '')->isConfigured());
|
|
}
|
|
|
|
#[Test]
|
|
public function it_is_not_configured_when_token_is_empty(): void
|
|
{
|
|
$this->assertFalse($this->makeProvider(token: '')->isConfigured());
|
|
}
|
|
|
|
#[Test]
|
|
public function it_parses_contribution_days_from_the_graphql_response(): void
|
|
{
|
|
$client = $this->createStub(HttpClientInterface::class);
|
|
$client->method('request')->willReturn($this->stubGraphqlResponse([
|
|
['contributionDays' => [
|
|
['date' => '2024-06-10', 'contributionCount' => 4],
|
|
['date' => '2024-06-11', 'contributionCount' => 2],
|
|
]],
|
|
]));
|
|
|
|
$provider = $this->makeProvider(client: $client);
|
|
$result = $provider->resolveFetch($provider->startFetch());
|
|
|
|
$this->assertSame(4, $result['2024-06-10']);
|
|
$this->assertSame(2, $result['2024-06-11']);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_skips_days_with_zero_contributions(): void
|
|
{
|
|
$client = $this->createStub(HttpClientInterface::class);
|
|
$client->method('request')->willReturn($this->stubGraphqlResponse([
|
|
['contributionDays' => [
|
|
['date' => '2024-06-11', 'contributionCount' => 0],
|
|
]],
|
|
]));
|
|
|
|
$provider = $this->makeProvider(client: $client);
|
|
$result = $provider->resolveFetch($provider->startFetch());
|
|
|
|
$this->assertArrayNotHasKey('2024-06-11', $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_throws_service_unavailable_exception_on_graphql_errors(): void
|
|
{
|
|
$response = $this->createStub(ResponseInterface::class);
|
|
$response->method('toArray')->willReturn([
|
|
'errors' => [['message' => 'Bad credentials']],
|
|
]);
|
|
|
|
$client = $this->createStub(HttpClientInterface::class);
|
|
$client->method('request')->willReturn($response);
|
|
|
|
$this->expectException(ServiceUnavailableHttpException::class);
|
|
|
|
$provider = $this->makeProvider(client: $client);
|
|
$provider->resolveFetch($provider->startFetch());
|
|
}
|
|
|
|
#[Test]
|
|
public function it_returns_empty_when_response_has_no_weeks(): void
|
|
{
|
|
$client = $this->createStub(HttpClientInterface::class);
|
|
$client->method('request')->willReturn($this->stubGraphqlResponse([]));
|
|
|
|
$provider = $this->makeProvider(client: $client);
|
|
$result = $provider->resolveFetch($provider->startFetch());
|
|
|
|
$this->assertSame([], $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_narrows_the_graphql_query_window_when_since_and_until_are_given(): void
|
|
{
|
|
$since = new \DateTimeImmutable('2024-01-01');
|
|
$until = new \DateTimeImmutable('2024-01-31');
|
|
$capturedQuery = null;
|
|
|
|
$client = $this->createMock(HttpClientInterface::class);
|
|
$client->method('request')->willReturnCallback(
|
|
function (string $method, string $url, array $options) use (&$capturedQuery): ResponseInterface {
|
|
$capturedQuery = $options['json']['query'];
|
|
|
|
return $this->stubGraphqlResponse([]);
|
|
}
|
|
);
|
|
|
|
$provider = $this->makeProvider(client: $client);
|
|
$provider->startFetch($since, $until);
|
|
|
|
$this->assertStringContainsString('2024-01-01T00:00:00Z', $capturedQuery);
|
|
$this->assertStringContainsString('2024-01-31T23:59:59Z', $capturedQuery);
|
|
}
|
|
}
|