2f3268c0b7
Move all provider-related classes, enums, interface and trait into App\Service\Provider; move SvgRenderer into App\Service\Renderer. ContributionAggregator stays at the Service root as the orchestrator. Test namespaces and use statements updated to match. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
3.6 KiB
PHP
110 lines
3.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Service;
|
|
|
|
use App\Service\ContributionAggregator;
|
|
use App\Service\Provider\ProviderInterface;
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
#[CoversClass(ContributionAggregator::class)]
|
|
final class ContributionAggregatorTest extends TestCase
|
|
{
|
|
private LoggerInterface $logger;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
$this->logger = $this->createStub(LoggerInterface::class);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_returns_empty_array_when_no_providers_are_given(): void
|
|
{
|
|
$aggregator = new ContributionAggregator([], $this->logger);
|
|
|
|
$result = $aggregator->aggregate();
|
|
|
|
$this->assertSame([], $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_skips_unconfigured_providers(): void
|
|
{
|
|
$provider = $this->createStub(ProviderInterface::class);
|
|
$provider->method('isConfigured')->willReturn(false);
|
|
|
|
$aggregator = new ContributionAggregator([$provider], $this->logger);
|
|
|
|
$result = $aggregator->aggregate();
|
|
|
|
$this->assertSame([], $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_returns_contributions_from_a_configured_provider(): void
|
|
{
|
|
$provider = $this->createStub(ProviderInterface::class);
|
|
$provider->method('isConfigured')->willReturn(true);
|
|
$provider->method('fetch')->willReturn(['2024-01-01' => 3]);
|
|
|
|
$aggregator = new ContributionAggregator([$provider], $this->logger);
|
|
|
|
$result = $aggregator->aggregate();
|
|
|
|
$this->assertSame(['2024-01-01' => 3], $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_sums_contributions_from_multiple_providers_on_the_same_date(): void
|
|
{
|
|
$providerA = $this->createStub(ProviderInterface::class);
|
|
$providerA->method('isConfigured')->willReturn(true);
|
|
$providerA->method('fetch')->willReturn(['2024-01-01' => 3, '2024-01-02' => 1]);
|
|
|
|
$providerB = $this->createStub(ProviderInterface::class);
|
|
$providerB->method('isConfigured')->willReturn(true);
|
|
$providerB->method('fetch')->willReturn(['2024-01-01' => 2, '2024-01-03' => 5]);
|
|
|
|
$aggregator = new ContributionAggregator([$providerA, $providerB], $this->logger);
|
|
|
|
$result = $aggregator->aggregate();
|
|
|
|
$this->assertSame(['2024-01-01' => 5, '2024-01-02' => 1, '2024-01-03' => 5], $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_continues_fetching_remaining_providers_when_one_throws(): void
|
|
{
|
|
$failing = $this->createStub(ProviderInterface::class);
|
|
$failing->method('isConfigured')->willReturn(true);
|
|
$failing->method('fetch')->willThrowException(new \RuntimeException('Network error'));
|
|
|
|
$healthy = $this->createStub(ProviderInterface::class);
|
|
$healthy->method('isConfigured')->willReturn(true);
|
|
$healthy->method('fetch')->willReturn(['2024-01-01' => 7]);
|
|
|
|
$aggregator = new ContributionAggregator([$failing, $healthy], $this->logger);
|
|
|
|
$result = $aggregator->aggregate();
|
|
|
|
$this->assertSame(['2024-01-01' => 7], $result);
|
|
}
|
|
|
|
#[Test]
|
|
public function it_logs_a_warning_when_a_provider_fetch_throws(): void
|
|
{
|
|
$logger = $this->createMock(LoggerInterface::class);
|
|
$logger->expects($this->once())->method('warning');
|
|
|
|
$provider = $this->createStub(ProviderInterface::class);
|
|
$provider->method('isConfigured')->willReturn(true);
|
|
$provider->method('fetch')->willThrowException(new \RuntimeException('fail'));
|
|
|
|
(new ContributionAggregator([$provider], $logger))->aggregate();
|
|
}
|
|
}
|