codex ticket rework
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace Tests\Feature\Projects;
|
||||
|
||||
use App\Actions\ProjectTicketMetadata\InitializeProjectTicketMetadata;
|
||||
use App\Models\Project;
|
||||
use App\Models\Ticket;
|
||||
use App\Models\TicketFieldDefinition;
|
||||
use App\Models\TicketLinkType;
|
||||
use App\Models\TicketPriority;
|
||||
@@ -23,6 +25,9 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
|
||||
app(InitializeProjectTicketMetadata::class)->handle($project);
|
||||
|
||||
TicketType::factory()->for($project)->create([
|
||||
'name' => 'Bug',
|
||||
'position' => 3,
|
||||
@@ -39,8 +44,11 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
->assertOk()
|
||||
->assertInertia(fn (Assert $page) => $page
|
||||
->component('projects/settings/Metadata')
|
||||
->where('project.show_author', true)
|
||||
->where('project.show_story_points', true)
|
||||
->where('ticketTypes.0.name', 'Task')
|
||||
->where('ticketTypes.1.name', 'Parent')
|
||||
->where('ticketTypes.1.name', 'Release')
|
||||
->where('ticketTypes.1.is_parent', true)
|
||||
->where('ticketTypes.2.name', 'Bug')
|
||||
->where('ticketLinkTypes.0.label', 'Relates to'),
|
||||
);
|
||||
@@ -58,6 +66,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->post(route('projects.settings.metadata.types.store', $project), [
|
||||
'is_parent' => '1',
|
||||
'name' => 'Bug',
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
@@ -66,57 +75,245 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
$this->assertDatabaseHas('ticket_types', [
|
||||
'project_id' => $project->id,
|
||||
'name' => 'Bug',
|
||||
'is_builtin' => false,
|
||||
'system_key' => null,
|
||||
'is_parent' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure built-in ticket types can be renamed but not deleted.
|
||||
* Ensure type parent flags cannot be changed after creation.
|
||||
*/
|
||||
public function test_builtin_ticket_types_can_be_renamed_but_not_deleted(): void
|
||||
public function test_ticket_type_parent_flags_cannot_be_changed_after_creation(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
|
||||
app(InitializeProjectTicketMetadata::class)->handle($project);
|
||||
|
||||
$releaseType = $project->ticketTypes()
|
||||
->where('is_parent', true)
|
||||
->orderBy('position')
|
||||
->firstOrFail();
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$taskType = TicketType::query()
|
||||
->where('project_id', $project->id)
|
||||
->where('system_key', 'task')
|
||||
->first();
|
||||
|
||||
if ($taskType === null) {
|
||||
$this->actingAs($user)
|
||||
->get(route('projects.settings.metadata', $project))
|
||||
->assertOk();
|
||||
|
||||
$taskType = TicketType::query()
|
||||
->where('project_id', $project->id)
|
||||
->where('system_key', 'task')
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
$this->actingAs($user)
|
||||
->patch(route('projects.settings.metadata.types.update', [$project, $taskType]), [
|
||||
'name' => 'Standard Task',
|
||||
'position' => 1,
|
||||
->patch(route('projects.settings.metadata.types.update', [$project, $releaseType]), [
|
||||
'is_parent' => '0',
|
||||
'name' => 'Standard Release',
|
||||
'position' => 2,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHas('status', 'Standard Task type was updated.');
|
||||
->assertSessionHas('status', 'Standard Release type was updated.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_types', [
|
||||
'id' => $taskType->id,
|
||||
'name' => 'Standard Task',
|
||||
'system_key' => 'task',
|
||||
'is_builtin' => true,
|
||||
'id' => $releaseType->id,
|
||||
'name' => 'Standard Release',
|
||||
'is_parent' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure users with edit permission can update project ticket visibility settings.
|
||||
*/
|
||||
public function test_users_with_edit_permission_can_update_ticket_visibility_settings(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create([
|
||||
'show_author' => true,
|
||||
'show_story_points' => true,
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.metadata.types.destroy', [$project, $taskType]))
|
||||
->patch(route('projects.settings.metadata.update', $project), [
|
||||
'show_author' => false,
|
||||
'show_story_points' => false,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHas('status', 'Settings have been updated.');
|
||||
|
||||
$this->assertDatabaseHas('projects', [
|
||||
'id' => $project->id,
|
||||
'show_author' => false,
|
||||
'show_story_points' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure users without project admin permission cannot update ticket metadata settings.
|
||||
*/
|
||||
public function test_non_admin_users_cannot_update_ticket_metadata_settings(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['view']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->patch(route('projects.settings.metadata.update', $project), [
|
||||
'show_author' => false,
|
||||
'show_story_points' => false,
|
||||
])
|
||||
->assertForbidden();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the last remaining ticket type cannot be deleted.
|
||||
*/
|
||||
public function test_the_last_remaining_ticket_type_cannot_be_deleted(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
$ticketType = TicketType::factory()->for($project)->create([
|
||||
'name' => 'Solo',
|
||||
'position' => 1,
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.metadata.types.destroy', [$project, $ticketType]))
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHas('status', 'The last ticket type cannot be deleted.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_types', [
|
||||
'id' => $ticketType->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure deleting a used ticket type requires a replacement and reassigns affected tickets.
|
||||
*/
|
||||
public function test_used_ticket_types_require_replacement_before_deletion_and_reassign_tickets(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$author = 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();
|
||||
$releaseType = $project->ticketTypes()
|
||||
->where('is_parent', true)
|
||||
->orderBy('position')
|
||||
->firstOrFail();
|
||||
$ticket = Ticket::factory()->create([
|
||||
'author_user_id' => $author->id,
|
||||
'project_id' => $project->id,
|
||||
'sequence_number' => 1,
|
||||
'ticket_key' => 'CORE-1',
|
||||
'ticket_priority_id' => $priority->id,
|
||||
'ticket_type_id' => $taskType->id,
|
||||
'title' => 'Replace my type',
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->from(route('projects.settings.metadata', $project))
|
||||
->delete(route('projects.settings.metadata.types.destroy', [$project, $taskType]))
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHasErrors('replacement_ticket_type_id');
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.metadata.types.destroy', [$project, $taskType]), [
|
||||
'replacement_ticket_type_id' => $releaseType->id,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHas('status', 'Task type was removed.');
|
||||
|
||||
$this->assertDatabaseMissing('ticket_types', [
|
||||
'id' => $taskType->id,
|
||||
]);
|
||||
$this->assertDatabaseHas('tickets', [
|
||||
'id' => $ticket->id,
|
||||
'ticket_type_id' => $releaseType->id,
|
||||
]);
|
||||
$this->assertDatabaseHas('ticket_worklog_entries', [
|
||||
'ticket_id' => $ticket->id,
|
||||
'actor_id' => $user->id,
|
||||
'entry_type' => 'updated',
|
||||
'summary' => 'Updated type because Task type was deleted.',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure parent tickets cannot be reassigned to a non-parent replacement type during delete.
|
||||
*/
|
||||
public function test_parent_tickets_require_a_parent_enabled_replacement_type_when_deleting_types(): void
|
||||
{
|
||||
$user = User::factory()->create();
|
||||
$author = 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();
|
||||
$releaseType = $project->ticketTypes()
|
||||
->where('is_parent', true)
|
||||
->orderBy('position')
|
||||
->firstOrFail();
|
||||
$epicType = TicketType::factory()->for($project)->create([
|
||||
'is_parent' => true,
|
||||
'name' => 'Epic',
|
||||
'position' => 3,
|
||||
]);
|
||||
$parentTicket = Ticket::factory()->create([
|
||||
'author_user_id' => $author->id,
|
||||
'project_id' => $project->id,
|
||||
'sequence_number' => 1,
|
||||
'ticket_key' => 'CORE-1',
|
||||
'ticket_priority_id' => $priority->id,
|
||||
'ticket_type_id' => $releaseType->id,
|
||||
'title' => 'Parent ticket',
|
||||
]);
|
||||
Ticket::factory()->create([
|
||||
'author_user_id' => $author->id,
|
||||
'parent_ticket_id' => $parentTicket->id,
|
||||
'project_id' => $project->id,
|
||||
'sequence_number' => 2,
|
||||
'ticket_key' => 'CORE-2',
|
||||
'ticket_priority_id' => $priority->id,
|
||||
'ticket_type_id' => $taskType->id,
|
||||
'title' => 'Child ticket',
|
||||
]);
|
||||
|
||||
$this->syncProjectPermissions($project, $user, ['edit']);
|
||||
|
||||
$this->actingAs($user)
|
||||
->from(route('projects.settings.metadata', $project))
|
||||
->delete(route('projects.settings.metadata.types.destroy', [$project, $releaseType]), [
|
||||
'replacement_ticket_type_id' => $taskType->id,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHasErrors('replacement_ticket_type_id');
|
||||
|
||||
$this->actingAs($user)
|
||||
->delete(route('projects.settings.metadata.types.destroy', [$project, $releaseType]), [
|
||||
'replacement_ticket_type_id' => $epicType->id,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHas('status', 'Release type was removed.');
|
||||
|
||||
$this->assertDatabaseHas('tickets', [
|
||||
'id' => $parentTicket->id,
|
||||
'ticket_type_id' => $epicType->id,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure priorities can be created, updated, and deleted.
|
||||
*/
|
||||
@@ -163,7 +360,7 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
$user = User::factory()->create();
|
||||
$project = Project::factory()->create();
|
||||
$fieldDefinition = TicketFieldDefinition::factory()->for($project)->create([
|
||||
'label' => 'Story Points',
|
||||
'label' => 'Complexity',
|
||||
'field_type' => 'number',
|
||||
'position' => 1,
|
||||
]);
|
||||
@@ -180,16 +377,16 @@ class ProjectTicketMetadataSettingsControllerTest extends TestCase
|
||||
|
||||
$this->actingAs($user)
|
||||
->patch(route('projects.settings.metadata.fields.update', [$project, $fieldDefinition]), [
|
||||
'label' => 'Estimated Points',
|
||||
'label' => 'Risk score',
|
||||
'field_type' => 'number',
|
||||
'position' => 2,
|
||||
])
|
||||
->assertRedirect(route('projects.settings.metadata', $project))
|
||||
->assertSessionHas('status', 'Estimated Points field was updated.');
|
||||
->assertSessionHas('status', 'Risk score field was updated.');
|
||||
|
||||
$this->assertDatabaseHas('ticket_field_definitions', [
|
||||
'id' => $fieldDefinition->id,
|
||||
'label' => 'Estimated Points',
|
||||
'label' => 'Risk score',
|
||||
'field_type' => 'number',
|
||||
]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user