incr/tests/Feature/CountBackfillMigrationTest.php

154 lines
4.2 KiB
PHP
Raw Normal View History

2026-08-15 15:10:26 +02:00
<?php
declare(strict_types=1);
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Tests\TestCase;
class CountBackfillMigrationTest extends TestCase
{
use RefreshDatabase;
private const MIGRATION = __DIR__.'/../../database/migrations/2026_08_15_000002_add_count_to_trackers_drop_entries.php';
// Each require returns a fresh anonymous-class instance; there is no name to collide.
private function migration(): object
{
return require self::MIGRATION;
}
/**
* Rebuild the pre-migration shape: entries present, trackers without a count.
*/
private function revertToLedger(): void
{
$this->migration()->down();
}
private function makeTracker(): int
{
$userId = DB::table('users')->insertGetId([
'name' => 'Test',
'email' => 'ledger@example.test',
'password' => 'x',
'created_at' => now(),
'updated_at' => now(),
]);
return DB::table('trackers')->insertGetId([
'user_id' => $userId,
'label' => 'Counter',
'unit' => 'units',
'created_at' => now(),
'updated_at' => now(),
]);
}
private function addEntry(int $trackerId, string $quantity): void
{
DB::table('entries')->insert([
'tracker_id' => $trackerId,
'date' => now()->toDateString(),
'quantity' => $quantity,
'created_at' => now(),
'updated_at' => now(),
]);
}
private function countFor(int $trackerId): int
{
return (int) DB::table('trackers')->where('id', $trackerId)->value('count');
}
public function test_backfill_sums_entries_into_count(): void
{
$this->revertToLedger();
$trackerId = $this->makeTracker();
$this->addEntry($trackerId, '10');
$this->addEntry($trackerId, '15');
$this->migration()->up();
$this->assertSame(25, $this->countFor($trackerId));
}
public function test_backfill_rounds_fractional_totals(): void
{
$this->revertToLedger();
$trackerId = $this->makeTracker();
$this->addEntry($trackerId, '1.4');
$this->addEntry($trackerId, '1.2');
$this->migration()->up();
$this->assertSame(3, $this->countFor($trackerId));
}
public function test_backfill_clamps_negative_totals_to_zero(): void
{
$this->revertToLedger();
$trackerId = $this->makeTracker();
$this->addEntry($trackerId, '5');
$this->addEntry($trackerId, '-9');
$this->migration()->up();
$this->assertSame(0, $this->countFor($trackerId));
}
public function test_tracker_without_entries_defaults_to_zero(): void
{
$this->revertToLedger();
$trackerId = $this->makeTracker();
$this->migration()->up();
$this->assertSame(0, $this->countFor($trackerId));
}
public function test_up_drops_the_entries_table(): void
{
$this->revertToLedger();
$this->assertTrue(Schema::hasTable('entries'));
$this->migration()->up();
$this->assertFalse(Schema::hasTable('entries'));
$this->assertTrue(Schema::hasColumn('trackers', 'count'));
}
// The two down() tests start from the migrated schema, so they do not call revertToLedger().
public function test_down_recreates_entries_from_count(): void
{
$trackerId = $this->makeTracker();
DB::table('trackers')->where('id', $trackerId)->update(['count' => 7]);
$this->migration()->down();
$this->assertTrue(Schema::hasTable('entries'));
$this->assertFalse(Schema::hasColumn('trackers', 'count'));
$entries = DB::table('entries')->where('tracker_id', $trackerId)->get();
$this->assertCount(1, $entries);
$this->assertSame(7.0, (float) $entries->first()->quantity);
}
public function test_down_writes_no_entry_for_a_zero_count(): void
{
$trackerId = $this->makeTracker();
$this->migration()->down();
$this->assertSame(0, DB::table('entries')->where('tracker_id', $trackerId)->count());
}
}