1
0

MigrationBackfillTest.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Tests\Db;
  4. use PDO;
  5. use PHPUnit\Framework\TestCase;
  6. /**
  7. * Phase 12: verify migration 002 backfills active_days_mask from the legacy
  8. * free-form max_working_days values, and clamps half-day values to whole
  9. * integers in 0..5 without dropping rows or widening beyond Mo–Fr.
  10. *
  11. * We start from a 001-only schema (no active_days_mask column), hand-insert
  12. * a row per legacy value we care about, then apply 002 and read back.
  13. */
  14. final class MigrationBackfillTest extends TestCase
  15. {
  16. private function makeLegacyDb(): PDO
  17. {
  18. $pdo = new PDO('sqlite::memory:', null, null, [
  19. PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
  20. PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
  21. PDO::ATTR_EMULATE_PREPARES => false,
  22. ]);
  23. $pdo->exec('PRAGMA foreign_keys = ON');
  24. $sql = file_get_contents(__DIR__ . '/../../migrations/001_init.sql');
  25. $this->assertNotFalse($sql, 'read 001_init.sql');
  26. $pdo->exec($sql);
  27. return $pdo;
  28. }
  29. /** @return list<array{legacyDays:float, expectedMask:int, expectedDays:float}> */
  30. public static function cases(): array
  31. {
  32. return [
  33. // whole numbers 0..5 stay as themselves
  34. ['legacyDays' => 0.0, 'expectedMask' => 0, 'expectedDays' => 0.0],
  35. ['legacyDays' => 1.0, 'expectedMask' => 0b1, 'expectedDays' => 1.0],
  36. ['legacyDays' => 3.0, 'expectedMask' => 0b111, 'expectedDays' => 3.0],
  37. ['legacyDays' => 5.0, 'expectedMask' => 31, 'expectedDays' => 5.0],
  38. // half-days round up (2.5 → 3, 4.5 → 5)
  39. ['legacyDays' => 2.5, 'expectedMask' => 0b111, 'expectedDays' => 3.0],
  40. ['legacyDays' => 4.5, 'expectedMask' => 31, 'expectedDays' => 5.0],
  41. // 0.5 rounds up to 1
  42. ['legacyDays' => 0.5, 'expectedMask' => 0b1, 'expectedDays' => 1.0],
  43. ];
  44. }
  45. public function testBackfillMapsLegacyValuesToMask(): void
  46. {
  47. $pdo = $this->makeLegacyDb();
  48. // Seed a sprint + one week per case.
  49. $pdo->exec("INSERT INTO sprints (id, name, start_date, end_date, reserve_fraction, is_archived, created_at, updated_at)
  50. VALUES (1, 'S', '2026-01-05', '2026-03-30', 0.2, 0, '2026-01-01T00:00:00Z', '2026-01-01T00:00:00Z')");
  51. $insertWeek = $pdo->prepare(
  52. 'INSERT INTO sprint_weeks (id, sprint_id, sort_order, iso_week, start_date, max_working_days)
  53. VALUES (?, 1, ?, ?, ?, ?)'
  54. );
  55. $cases = self::cases();
  56. foreach ($cases as $i => $c) {
  57. $insertWeek->execute([
  58. $i + 1, $i + 1, 1, '2026-01-05', $c['legacyDays'],
  59. ]);
  60. }
  61. // Apply migration 002.
  62. $sql = file_get_contents(__DIR__ . '/../../migrations/002_sprint_week_active_days.sql');
  63. $this->assertNotFalse($sql, 'read 002');
  64. $pdo->exec($sql);
  65. // The column now exists; legacy rows were rewritten.
  66. $rows = $pdo->query('SELECT id, max_working_days, active_days_mask FROM sprint_weeks ORDER BY id')->fetchAll();
  67. $this->assertCount(count($cases), $rows);
  68. foreach ($cases as $i => $c) {
  69. $row = $rows[$i];
  70. $this->assertSame(
  71. $c['expectedMask'],
  72. (int) $row['active_days_mask'],
  73. "mask for legacy={$c['legacyDays']} (row id={$row['id']})",
  74. );
  75. $this->assertEqualsWithDelta(
  76. $c['expectedDays'],
  77. (float) $row['max_working_days'],
  78. 1e-9,
  79. "days for legacy={$c['legacyDays']}",
  80. );
  81. }
  82. }
  83. }