200 lines
7.5 KiB
PHP
200 lines
7.5 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\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 = $project->ticketPriorities()->firstOrFail();
|
|
$taskType = $project->ticketTypes()
|
|
->where('is_parent', false)
|
|
->orderBy('position')
|
|
->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 = $project->ticketPriorities()->firstOrFail();
|
|
$otherPriority = $otherProject->ticketPriorities()->firstOrFail();
|
|
$taskType = $project->ticketTypes()
|
|
->where('is_parent', false)
|
|
->orderBy('position')
|
|
->firstOrFail();
|
|
$otherTaskType = $otherProject->ticketTypes()
|
|
->where('is_parent', false)
|
|
->orderBy('position')
|
|
->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 = $project->ticketPriorities()->firstOrFail();
|
|
$taskType = $project->ticketTypes()
|
|
->where('is_parent', false)
|
|
->orderBy('position')
|
|
->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.',
|
|
]);
|
|
}
|
|
}
|