feat(store): finish ContributionStore and add typed entities
- Fix add()'s upsert SQL (was invalid SQL from typos) and use it for the new merge() as well. - Fix remove()'s missing FROM keyword and drop the unindexed LIKE scan in favor of an exact match. - Add latestDate(), all() with optional sinceDays filtering, and prune() with a retention-day cutoff. - Add Contribution (immutable value object) and ContributionCollection (IteratorAggregate + Countable) so all() returns something typed instead of a raw date => count array. - Cover all of the above with unit tests against an in-memory SQLite database.
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Entity;
|
||||
|
||||
use App\Entity\Contribution;
|
||||
use App\Entity\ContributionCollection;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ContributionCollectionTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function it_counts_zero_when_empty(): void
|
||||
{
|
||||
$collection = new ContributionCollection();
|
||||
|
||||
$this->assertCount(0, $collection);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_counts_the_contributions_it_wraps(): void
|
||||
{
|
||||
$collection = new ContributionCollection(
|
||||
new Contribution('github', 1_700_000_000, 1),
|
||||
new Contribution('github', 1_700_086_400, 2),
|
||||
);
|
||||
|
||||
$this->assertCount(2, $collection);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_is_iterable_over_its_contributions(): void
|
||||
{
|
||||
$first = new Contribution('github', 1_700_000_000, 1);
|
||||
$second = new Contribution('gitlab', 1_700_086_400, 2);
|
||||
|
||||
$collection = new ContributionCollection($first, $second);
|
||||
|
||||
$this->assertSame([$first, $second], iterator_to_array($collection));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Entity;
|
||||
|
||||
use App\Entity\Contribution;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ContributionTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function it_exposes_provider_date_and_count(): void
|
||||
{
|
||||
$contribution = new Contribution('github', 1_700_000_000, 4);
|
||||
|
||||
$this->assertSame('github', $contribution->provider);
|
||||
$this->assertSame(1_700_000_000, $contribution->date);
|
||||
$this->assertSame(4, $contribution->count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Service;
|
||||
|
||||
use App\Service\ContributionStore;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class ContributionStoreTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
public function it_stores_and_retrieves_a_contribution(): void
|
||||
{
|
||||
$store = new ContributionStore(':memory:');
|
||||
|
||||
$store->add('github', 1_700_000_000, 4);
|
||||
$all = $store->all('github');
|
||||
|
||||
$this->assertCount(1, $all);
|
||||
$this->assertSame(4, iterator_to_array($all)[0]->count);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_upserts_on_a_repeated_date(): void
|
||||
{
|
||||
$store = new ContributionStore(':memory:');
|
||||
|
||||
$store->add('github', 1_700_000_000, 4);
|
||||
$store->add('github', 1_700_000_000, 9);
|
||||
$all = $store->all('github');
|
||||
|
||||
$this->assertCount(1, $all);
|
||||
$this->assertSame(9, iterator_to_array($all)[0]->count);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_removes_a_contribution(): void
|
||||
{
|
||||
$store = new ContributionStore(':memory:');
|
||||
|
||||
$store->add('github', 1_700_000_000, 4);
|
||||
$store->remove('github', 1_700_000_000);
|
||||
|
||||
$this->assertCount(0, $store->all('github'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_merges_a_batch_of_date_counts(): void
|
||||
{
|
||||
$store = new ContributionStore(':memory:');
|
||||
|
||||
$store->merge('github', [1_700_000_000 => 1, 1_700_086_400 => 2]);
|
||||
|
||||
$this->assertCount(2, $store->all('github'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_reports_the_latest_date_for_a_provider(): void
|
||||
{
|
||||
$store = new ContributionStore(':memory:');
|
||||
|
||||
$store->add('github', 1_700_000_000, 1);
|
||||
$store->add('github', 1_700_086_400, 2);
|
||||
|
||||
$this->assertSame(1_700_086_400, $store->latestDate('github'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_returns_null_latest_date_when_provider_has_no_rows(): void
|
||||
{
|
||||
$store = new ContributionStore(':memory:');
|
||||
|
||||
$this->assertNull($store->latestDate('github'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_filters_all_by_since_days(): void
|
||||
{
|
||||
$store = new ContributionStore(':memory:');
|
||||
$now = time();
|
||||
|
||||
$store->add('github', $now - 10 * 86400, 1);
|
||||
$store->add('github', $now - 400 * 86400, 2);
|
||||
|
||||
$this->assertCount(1, $store->all('github', sinceDays: 30));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_does_not_prune_when_retention_is_unset(): void
|
||||
{
|
||||
$store = new ContributionStore(':memory:', retentionDays: null);
|
||||
|
||||
$store->add('github', time() - 1_000 * 86400, 1);
|
||||
$store->prune();
|
||||
|
||||
$this->assertCount(1, $store->all('github'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_prunes_rows_older_than_the_retention_window(): void
|
||||
{
|
||||
$store = new ContributionStore(':memory:', retentionDays: 30);
|
||||
$now = time();
|
||||
|
||||
$store->add('github', $now - 10 * 86400, 1);
|
||||
$store->add('github', $now - 40 * 86400, 2);
|
||||
$store->prune();
|
||||
|
||||
$this->assertCount(1, $store->all('github'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user