1
0

MigrationBackfillTest.php 4.0 KB

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