makeDb(); $sprints = new SprintRepository($pdo); $workers = new WorkerRepository($pdo); $sw = new SprintWorkerRepository($pdo); $tasks = new TaskRepository($pdo); $repo = new TaskAssignmentRepository($pdo); $sprint = $sprints->create('S', '2026-01-05', '2026-01-30', 0.2); $sprints->materializeWeeks($sprint->id, '2026-01-05', 4); $worker = $workers->create('Alice', true, 0.0); $sworker = $sw->add($sprint->id, $worker->id, 0.0); $task = $tasks->create($sprint->id, 'Build thing', null, 1); return [$pdo, $repo, $sprint->id, $sworker->id, $task->id, $worker->id]; } public function testUpsertStatusOnEmptyCellWithDefaultIsNoop(): void { [, $repo, , $swId, $taskId] = $this->seed(); $r = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_ZUGEWIESEN); $this->assertSame('NOOP', $r['action']); $this->assertNull($r['before']); $this->assertNull($r['after']); $this->assertNull($repo->find($taskId, $swId)); } public function testUpsertStatusOnEmptyCellWithExplicitCreatesZeroDayRow(): void { [, $repo, , $swId, $taskId] = $this->seed(); $r = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET); $this->assertSame('CREATE', $r['action']); $this->assertNull($r['before']); $this->assertNotNull($r['after']); $this->assertSame(0.0, $r['after']->days); $this->assertSame(TaskAssignment::STATUS_GESTARTET, $r['after']->status); // Reload to confirm persistence. $loaded = $repo->find($taskId, $swId); $this->assertNotNull($loaded); $this->assertSame(TaskAssignment::STATUS_GESTARTET, $loaded->status); } public function testUpsertStatusUnchangedIsNoop(): void { [, $repo, , $swId, $taskId] = $this->seed(); $repo->upsert($taskId, $swId, 1.5); $first = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET); $this->assertSame('UPDATE', $first['action']); $again = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET); $this->assertSame('NOOP', $again['action']); $this->assertSame(TaskAssignment::STATUS_GESTARTET, $again['after']->status); } public function testUpsertStatusUpdatesExistingRowAndKeepsDays(): void { [, $repo, , $swId, $taskId] = $this->seed(); $repo->upsert($taskId, $swId, 2.5); $r = $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_ABGESCHLOSSEN); $this->assertSame('UPDATE', $r['action']); $this->assertSame(2.5, $r['before']->days); $this->assertSame(TaskAssignment::STATUS_ZUGEWIESEN, $r['before']->status); $this->assertSame(2.5, $r['after']->days); $this->assertSame(TaskAssignment::STATUS_ABGESCHLOSSEN, $r['after']->status); } public function testUpsertDaysOnExistingPreservesStatus(): void { [, $repo, , $swId, $taskId] = $this->seed(); $repo->upsert($taskId, $swId, 1.0); $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_GESTARTET); $r = $repo->upsert($taskId, $swId, 3.0); $this->assertSame('UPDATE', $r['action']); $this->assertSame(TaskAssignment::STATUS_GESTARTET, $r['after']->status); } public function testUpsertStatusRejectsUnknownValue(): void { [, $repo, , $swId, $taskId] = $this->seed(); $this->expectException(InvalidArgumentException::class); $repo->upsertStatus($taskId, $swId, 'in-flight'); } public function testStatusGridForSprintGroupsByTaskAndWorker(): void { [, $repo, $sprintId, $swId, $taskId] = $this->seed(); $repo->upsert($taskId, $swId, 1.0); $repo->upsertStatus($taskId, $swId, TaskAssignment::STATUS_ABGEBROCHEN); $grid = $repo->statusGridForSprint($sprintId); $this->assertArrayHasKey($taskId, $grid); $this->assertArrayHasKey($swId, $grid[$taskId]); $this->assertSame(TaskAssignment::STATUS_ABGEBROCHEN, $grid[$taskId][$swId]); } public function testHydrateOnFreshUpsertCarriesDefaultStatus(): void { [, $repo, , $swId, $taskId] = $this->seed(); $r = $repo->upsert($taskId, $swId, 1.0); $this->assertSame('CREATE', $r['action']); $this->assertSame(TaskAssignment::STATUS_ZUGEWIESEN, $r['after']->status); $loaded = $repo->find($taskId, $swId); $this->assertNotNull($loaded); $this->assertSame(TaskAssignment::STATUS_ZUGEWIESEN, $loaded->status); } }