Files
KanbanGit/tests/Feature/Projects/ProjectTicketLinkControllerTest.php
T
2026-04-12 19:21:04 +02:00

201 lines
7.6 KiB
PHP

<?php
namespace Tests\Feature\Projects;
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
use App\Models\Project;
use App\Models\Ticket;
use App\Models\TicketLink;
use App\Models\TicketLinkType;
use App\Models\TicketPriority;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class ProjectTicketLinkControllerTest extends TestCase
{
use RefreshDatabase;
/**
* Ensure users with edit permission can link tickets and create a worklog entry.
*/
public function test_users_with_edit_permission_can_link_tickets(): void
{
$user = User::factory()->create();
$project = Project::factory()->create([
'key' => 'CORE',
]);
app(InitializeProjectTicketMetadata::class)->handle($project);
$priority = TicketPriority::factory()->for($project)->create([
'name' => 'Medium',
'position' => 1,
]);
$taskType = $project->ticketTypes()->where('system_key', 'task')->firstOrFail();
$ticketLinkType = TicketLinkType::factory()->for($project)->create([
'label' => 'Relates to',
'position' => 1,
]);
$ticket = Ticket::factory()->create([
'project_id' => $project->id,
'sequence_number' => 1,
'ticket_key' => 'CORE-1',
'ticket_priority_id' => $priority->id,
'ticket_type_id' => $taskType->id,
]);
$linkedTicket = Ticket::factory()->create([
'project_id' => $project->id,
'sequence_number' => 2,
'ticket_key' => 'CORE-2',
'ticket_priority_id' => $priority->id,
'ticket_type_id' => $taskType->id,
]);
$this->syncProjectPermissions($project, $user, ['edit', 'view']);
$this->actingAs($user)
->post(route('projects.tickets.links.store', [$project, $ticket]), [
'linked_ticket_id' => $linkedTicket->id,
'ticket_link_type_id' => $ticketLinkType->id,
])
->assertRedirect(route('projects.tickets.show', [$project, $ticket]))
->assertSessionHas('status', 'Relates to link was saved.');
$this->assertDatabaseHas('ticket_links', [
'source_ticket_id' => min($ticket->id, $linkedTicket->id),
'target_ticket_id' => max($ticket->id, $linkedTicket->id),
'ticket_link_type_id' => $ticketLinkType->id,
]);
$this->assertDatabaseHas('ticket_worklog_entries', [
'ticket_id' => $ticket->id,
'actor_id' => $user->id,
'entry_type' => 'link_updated',
'summary' => 'Linked CORE-2 as Relates to.',
]);
}
/**
* Ensure ticket links cannot target the current ticket or a ticket from another project.
*/
public function test_ticket_links_must_target_other_tickets_within_the_same_project(): void
{
$user = User::factory()->create();
$project = Project::factory()->create();
$otherProject = Project::factory()->create();
app(InitializeProjectTicketMetadata::class)->handle($project);
app(InitializeProjectTicketMetadata::class)->handle($otherProject);
$priority = TicketPriority::factory()->for($project)->create([
'name' => 'Medium',
'position' => 1,
]);
$otherPriority = TicketPriority::factory()->for($otherProject)->create([
'name' => 'Medium',
'position' => 1,
]);
$taskType = $project->ticketTypes()->where('system_key', 'task')->firstOrFail();
$otherTaskType = $otherProject->ticketTypes()->where('system_key', 'task')->firstOrFail();
$ticketLinkType = TicketLinkType::factory()->for($project)->create([
'label' => 'Relates to',
'position' => 1,
]);
$ticket = Ticket::factory()->create([
'project_id' => $project->id,
'sequence_number' => 1,
'ticket_key' => 'APP-1',
'ticket_priority_id' => $priority->id,
'ticket_type_id' => $taskType->id,
]);
$foreignTicket = Ticket::factory()->create([
'project_id' => $otherProject->id,
'sequence_number' => 1,
'ticket_key' => 'OPS-1',
'ticket_priority_id' => $otherPriority->id,
'ticket_type_id' => $otherTaskType->id,
]);
$this->syncProjectPermissions($project, $user, ['edit', 'view']);
$this->actingAs($user)
->from(route('projects.tickets.show', [$project, $ticket]))
->post(route('projects.tickets.links.store', [$project, $ticket]), [
'linked_ticket_id' => $ticket->id,
'ticket_link_type_id' => $ticketLinkType->id,
])
->assertRedirect(route('projects.tickets.show', [$project, $ticket]))
->assertSessionHasErrors('linked_ticket_id');
$this->actingAs($user)
->from(route('projects.tickets.show', [$project, $ticket]))
->post(route('projects.tickets.links.store', [$project, $ticket]), [
'linked_ticket_id' => $foreignTicket->id,
'ticket_link_type_id' => $ticketLinkType->id,
])
->assertRedirect(route('projects.tickets.show', [$project, $ticket]))
->assertSessionHasErrors('linked_ticket_id');
}
/**
* Ensure users with edit permission can remove ticket links.
*/
public function test_users_with_edit_permission_can_remove_ticket_links(): void
{
$user = User::factory()->create();
$project = Project::factory()->create([
'key' => 'CORE',
]);
app(InitializeProjectTicketMetadata::class)->handle($project);
$priority = TicketPriority::factory()->for($project)->create([
'name' => 'Medium',
'position' => 1,
]);
$taskType = $project->ticketTypes()->where('system_key', 'task')->firstOrFail();
$ticketLinkType = TicketLinkType::factory()->for($project)->create([
'label' => 'Relates to',
'position' => 1,
]);
$ticket = Ticket::factory()->create([
'project_id' => $project->id,
'sequence_number' => 1,
'ticket_key' => 'CORE-1',
'ticket_priority_id' => $priority->id,
'ticket_type_id' => $taskType->id,
]);
$linkedTicket = Ticket::factory()->create([
'project_id' => $project->id,
'sequence_number' => 2,
'ticket_key' => 'CORE-2',
'ticket_priority_id' => $priority->id,
'ticket_type_id' => $taskType->id,
]);
$ticketLink = TicketLink::factory()->create([
'source_ticket_id' => min($ticket->id, $linkedTicket->id),
'target_ticket_id' => max($ticket->id, $linkedTicket->id),
'ticket_link_type_id' => $ticketLinkType->id,
]);
$this->syncProjectPermissions($project, $user, ['edit', 'view']);
$this->actingAs($user)
->delete(route('projects.tickets.links.destroy', [$project, $ticket, $ticketLink]))
->assertRedirect(route('projects.tickets.show', [$project, $ticket]))
->assertSessionHas('status', 'Relates to link was removed.');
$this->assertDatabaseMissing('ticket_links', [
'id' => $ticketLink->id,
]);
$this->assertDatabaseHas('ticket_worklog_entries', [
'ticket_id' => $ticket->id,
'actor_id' => $user->id,
'entry_type' => 'link_removed',
'summary' => 'Removed Relates to link to CORE-2.',
]);
}
}