approved()->for($project)->create(['revision_number' => $number]); ProjectFile::factory()->for($revision, 'revision')->create(); return $revision; } public function test_admin_can_confirm_an_approved_revision_as_current(): void { $admin = User::factory()->create(['role' => 'admin']); $project = Project::factory()->create(); $revision = $this->approvedRevisionWithFile($project); $this->actingAs($admin) ->post(route('projects.revisions.confirm-current', [$project, $revision]), ['lock_version' => 0]) ->assertRedirect(route('projects.show', $project)); $project->refresh(); $this->assertSame($revision->id, $project->current_revision_id); $this->assertSame(1, $project->lock_version); $this->assertDatabaseHas('project_events', [ 'project_id' => $project->id, 'event_type' => 'current_revision_confirmed', 'subject_type' => ProjectRevision::class, 'subject_id' => $revision->id, ]); $event = $project->events()->where('event_type', 'current_revision_confirmed')->first(); $this->assertSame(['previous_current_revision_id' => null, 'new_current_revision_id' => $revision->id], $event->metadata); } public function test_rejects_an_unapproved_revision(): void { $admin = User::factory()->create(['role' => 'admin']); $project = Project::factory()->create(); $revision = ProjectRevision::factory()->for($project)->create(['revision_number' => 1]); ProjectFile::factory()->for($revision, 'revision')->create(); $this->actingAs($admin) ->post(route('projects.revisions.confirm-current', [$project, $revision]), ['lock_version' => 0]) ->assertSessionHasErrors('revision_id'); $this->assertNull($project->fresh()->current_revision_id); } public function test_rejects_an_approved_revision_with_no_files(): void { // Approving through the normal endpoint already requires at least one file, // but the confirm endpoint must not trust that invariant blindly. $admin = User::factory()->create(['role' => 'admin']); $project = Project::factory()->create(); $revision = ProjectRevision::factory()->approved()->for($project)->create(['revision_number' => 1]); $this->actingAs($admin) ->post(route('projects.revisions.confirm-current', [$project, $revision]), ['lock_version' => 0]) ->assertSessionHasErrors('revision_id'); $this->assertNull($project->fresh()->current_revision_id); } public function test_rejects_a_revision_belonging_to_a_different_project(): void { $admin = User::factory()->create(['role' => 'admin']); $projectA = Project::factory()->create(); $projectB = Project::factory()->create(); $revision = $this->approvedRevisionWithFile($projectB); $this->actingAs($admin) ->post(route('projects.revisions.confirm-current', [$projectA, $revision]), ['lock_version' => 0]) ->assertNotFound(); $this->assertNull($projectA->fresh()->current_revision_id); } public function test_stale_lock_version_is_rejected_as_a_conflict(): void { $admin = User::factory()->create(['role' => 'admin']); $project = Project::factory()->create(); $revision = $this->approvedRevisionWithFile($project); $response = $this->actingAs($admin) ->post(route('projects.revisions.confirm-current', [$project, $revision]), ['lock_version' => 5]); $response->assertStatus(409); $this->assertNull($project->fresh()->current_revision_id); } /** * A real HTML