create(); $project = Project::factory()->create([ 'key' => 'AUTH', ]); app(InitializeProjectTicketMetadata::class)->handle($project); BoardColumn::factory()->for($project)->create([ 'name' => 'Todo', 'position' => 1, ]); $priority = TicketPriority::factory()->for($project)->create([ 'name' => 'Medium', 'position' => 1, ]); $ticket = Ticket::factory()->create([ 'project_id' => $project->id, 'ticket_priority_id' => $priority->id, 'ticket_type_id' => $project->ticketTypes()->firstOrFail()->id, 'ticket_key' => 'AUTH-1', 'sequence_number' => 1, 'title' => 'Seed backlog item', ]); app(ProjectPermissionService::class)->syncUserPermissions($project, $user, ['view']); $this->actingAs($user) ->get(route('projects.backlog.index', $project)) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->component('projects/Backlog') ->where('boardColumns.0.name', 'Todo') ->where('canEditTickets', false) ->where('project.id', $project->id) ->where('tickets.0.ticket_key', $ticket->ticket_key) ->where('canCreateTickets', false), ); } /** * Ensure creating tickets requires the project create permission. */ public function test_creating_tickets_requires_project_create_permission(): void { $user = User::factory()->create(); $project = Project::factory()->create(); app(InitializeProjectTicketMetadata::class)->handle($project); $priority = TicketPriority::factory()->for($project)->create([ 'name' => 'Medium', 'position' => 1, ]); app(ProjectPermissionService::class)->syncUserPermissions($project, $user, ['view']); $this->actingAs($user) ->post(route('projects.backlog.store', $project), [ 'title' => 'New ticket', 'description' => 'Description', 'acceptance_criteria' => 'Criteria', 'ticket_type_id' => $project->ticketTypes()->firstOrFail()->id, 'ticket_priority_id' => $priority->id, ]) ->assertForbidden(); } /** * Ensure project-local keys increment and backlog accepts duplicate titles. */ public function test_users_with_create_permission_can_add_duplicate_titled_tickets_to_backlog(): void { $user = User::factory()->create(); $project = Project::factory()->create([ 'key' => 'AUTH', ]); app(InitializeProjectTicketMetadata::class)->handle($project); $priority = TicketPriority::factory()->for($project)->create([ 'name' => 'High', 'position' => 1, ]); app(ProjectPermissionService::class)->syncUserPermissions($project, $user, ['create', 'view']); $payload = [ 'title' => 'Same title', 'description' => 'Description', 'acceptance_criteria' => 'Criteria', 'ticket_type_id' => $project->ticketTypes()->firstOrFail()->id, 'ticket_priority_id' => $priority->id, ]; $this->actingAs($user) ->post(route('projects.backlog.store', $project), $payload) ->assertRedirect(route('projects.backlog.index', $project)) ->assertSessionHas('status', 'AUTH-1 was added to the backlog.'); $this->actingAs($user) ->post(route('projects.backlog.store', $project), $payload) ->assertRedirect(route('projects.backlog.index', $project)) ->assertSessionHas('status', 'AUTH-2 was added to the backlog.'); $this->assertDatabaseHas('tickets', [ 'project_id' => $project->id, 'ticket_key' => 'AUTH-1', 'title' => 'Same title', 'board_column_id' => null, ]); $this->assertDatabaseHas('tickets', [ 'project_id' => $project->id, 'ticket_key' => 'AUTH-2', 'title' => 'Same title', 'board_column_id' => null, ]); } }