TaskAssignmentRepositoryTest.php 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  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\Repositories;
  12. use App\Domain\TaskAssignment;
  13. use App\Repositories\SprintRepository;
  14. use App\Repositories\SprintWorkerRepository;
  15. use App\Repositories\TaskAssignmentRepository;
  16. use App\Repositories\TaskRepository;
  17. use App\Repositories\WorkerRepository;
  18. use App\Tests\TestCase;
  19. use InvalidArgumentException;
  20. use PDO;
  21. /**
  22. * Phase 18: per-cell status on task_assignments. Verifies upsertStatus
  23. * semantics, the no-op rule, the "row created with days=0 when status is
  24. * set on an empty cell" path, and the new statusGridForSprint reader.
  25. */
  26. final class TaskAssignmentRepositoryTest extends TestCase
  27. {
  28. /** @return array{PDO,TaskAssignmentRepository,int,int,int,int} pdo, repo, sprintId, swId, taskId, workerId */
  29. private function seed(): array
  30. {
  31. $pdo = $this->makeDb();
  32. $sprints = new SprintRepository($pdo);
  33. $workers = new WorkerRepository($pdo);
  34. $sw = new SprintWorkerRepository($pdo);
  35. $tasks = new TaskRepository($pdo);
  36. $repo = new TaskAssignmentRepository($pdo);
  37. $sprint = $sprints->create('S', '2026-01-05', '2026-01-30', 0.2);
  38. $sprints->materializeWeeks($sprint->id, '2026-01-05', 4);
  39. $worker = $workers->create('Alice', true, 0.0);
  40. $sworker = $sw->add($sprint->id, $worker->id, 0.0);
  41. $task = $tasks->create($sprint->id, 'Build thing', null, 1);
  42. return [$pdo, $repo, $sprint->id, $sworker->id, $task->id, $worker->id];
  43. }
  44. public function testUpsertStatusOnEmptyCellWithDefaultIsNoop(): void
  45. {
  46. [, $repo, , $swId, $taskId] = $this->seed();
  47. $r = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_ZUGEWIESEN);
  48. $this->assertSame('NOOP', $r['action']);
  49. $this->assertNull($r['before']);
  50. $this->assertNull($r['after']);
  51. $this->assertNull($repo->find($taskId, $swId));
  52. }
  53. public function testUpsertStatusOnEmptyCellWithExplicitCreatesZeroDayRow(): void
  54. {
  55. [, $repo, , $swId, $taskId] = $this->seed();
  56. $r = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET);
  57. $this->assertSame('CREATE', $r['action']);
  58. $this->assertNull($r['before']);
  59. $this->assertNotNull($r['after']);
  60. $this->assertSame(0.0, $r['after']->days);
  61. $this->assertSame(TaskAssignment::STATUS_GESTARTET, $r['after']->status);
  62. // Reload to confirm persistence.
  63. $loaded = $repo->find($taskId, $swId);
  64. $this->assertNotNull($loaded);
  65. $this->assertSame(TaskAssignment::STATUS_GESTARTET, $loaded->status);
  66. }
  67. public function testUpsertStatusUnchangedIsNoop(): void
  68. {
  69. [, $repo, , $swId, $taskId] = $this->seed();
  70. $repo->upsert($taskId, $swId, 1.5);
  71. $first = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET);
  72. $this->assertSame('UPDATE', $first['action']);
  73. $again = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET);
  74. $this->assertSame('NOOP', $again['action']);
  75. $this->assertSame(TaskAssignment::STATUS_GESTARTET, $again['after']->status);
  76. }
  77. public function testUpsertStatusUpdatesExistingRowAndKeepsDays(): void
  78. {
  79. [, $repo, , $swId, $taskId] = $this->seed();
  80. $repo->upsert($taskId, $swId, 2.5);
  81. $r = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_ABGESCHLOSSEN);
  82. $this->assertSame('UPDATE', $r['action']);
  83. $this->assertSame(2.5, $r['before']->days);
  84. $this->assertSame(TaskAssignment::STATUS_ZUGEWIESEN, $r['before']->status);
  85. $this->assertSame(2.5, $r['after']->days);
  86. $this->assertSame(TaskAssignment::STATUS_ABGESCHLOSSEN, $r['after']->status);
  87. }
  88. public function testUpsertDaysOnExistingPreservesStatus(): void
  89. {
  90. [, $repo, , $swId, $taskId] = $this->seed();
  91. $repo->upsert($taskId, $swId, 1.0);
  92. $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET);
  93. $r = $repo->upsert($taskId, $swId, 3.0);
  94. $this->assertSame('UPDATE', $r['action']);
  95. $this->assertSame(TaskAssignment::STATUS_GESTARTET, $r['after']->status);
  96. }
  97. public function testUpsertStatusRejectsUnknownValue(): void
  98. {
  99. [, $repo, , $swId, $taskId] = $this->seed();
  100. $this->expectException(InvalidArgumentException::class);
  101. $repo->upsertStatus($taskId, $swId, 'in-flight');
  102. }
  103. public function testStatusGridForSprintGroupsByTaskAndWorker(): void
  104. {
  105. [, $repo, $sprintId, $swId, $taskId] = $this->seed();
  106. $repo->upsert($taskId, $swId, 1.0);
  107. $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_ABGEBROCHEN);
  108. $grid = $repo->statusGridForSprint($sprintId);
  109. $this->assertArrayHasKey($taskId, $grid);
  110. $this->assertArrayHasKey($swId, $grid[$taskId]);
  111. $this->assertSame(TaskAssignment::STATUS_ABGEBROCHEN, $grid[$taskId][$swId]);
  112. }
  113. public function testHydrateOnFreshUpsertCarriesDefaultStatus(): void
  114. {
  115. [, $repo, , $swId, $taskId] = $this->seed();
  116. $r = $repo->upsert($taskId, $swId, 1.0);
  117. $this->assertSame('CREATE', $r['action']);
  118. $this->assertSame(TaskAssignment::STATUS_ZUGEWIESEN, $r['after']->status);
  119. $loaded = $repo->find($taskId, $swId);
  120. $this->assertNotNull($loaded);
  121. $this->assertSame(TaskAssignment::STATUS_ZUGEWIESEN, $loaded->status);
  122. }
  123. }