2026-08-15 15:10:26 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace Tests\Feature;
|
|
|
|
|
|
2026-08-15 21:50:37 +02:00
|
|
|
use Illuminate\Database\Migrations\Migration;
|
2026-08-15 15:10:26 +02:00
|
|
|
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.
|
2026-08-15 21:50:37 +02:00
|
|
|
private function migration(): Migration
|
2026-08-15 15:10:26 +02:00
|
|
|
{
|
|
|
|
|
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());
|
|
|
|
|
}
|
|
|
|
|
}
|