create(['name' => 'Infrastruktura']); $otherTeam = Team::query()->create(['name' => 'Aplikacje']); $operator->teams()->attach($myTeam->id); $teamKeys = Livewire::actingAs($operator)->test(Queue::class) ->instance()->teams->pluck('name')->all(); expect($teamKeys)->toBe(['Infrastruktura']); }); test('an admin (even if also an operator) sees every team in the queue sidebar', function () { seedStatusesAndPriorities(); $admin = User::query()->create(['name' => 'Admin Op', 'email' => 'admin-op@example.com', 'roles' => ['operator', 'admin']]); Team::query()->create(['name' => 'Infrastruktura']); Team::query()->create(['name' => 'Aplikacje']); $teamNames = Livewire::actingAs($admin)->test(Queue::class) ->instance()->teams->pluck('name')->sort()->values()->all(); expect($teamNames)->toBe(['Aplikacje', 'Infrastruktura']); }); test('a non-admin operator only sees tickets from their own team, unrouted tickets, or ones assigned to them', function () { seedStatusesAndPriorities(); $operator = operatorUser('scoped-2@example.com'); $myTeam = Team::query()->create(['name' => 'Infrastruktura']); $otherTeam = Team::query()->create(['name' => 'Aplikacje']); $operator->teams()->attach($myTeam->id); $mine = makeTicket(['number' => '1001', 'team_id' => $myTeam->id]); $others = makeTicket(['number' => '1002', 'team_id' => $otherTeam->id]); $unrouted = makeTicket(['number' => '1003', 'team_id' => null]); $assignedToMeElsewhere = makeTicket(['number' => '1004', 'team_id' => $otherTeam->id, 'assignee_id' => $operator->id]); Livewire::actingAs($operator)->test(Queue::class) ->assertSee($mine->number) ->assertSee($unrouted->number) ->assertSee($assignedToMeElsewhere->number) ->assertDontSee($others->number); }); test('an admin operator still sees tickets from every team', function () { seedStatusesAndPriorities(); $admin = User::query()->create(['name' => 'Admin Op', 'email' => 'admin-op-2@example.com', 'roles' => ['operator', 'admin']]); $teamA = Team::query()->create(['name' => 'Infrastruktura']); $teamB = Team::query()->create(['name' => 'Aplikacje']); $ticketA = makeTicket(['number' => '2001', 'team_id' => $teamA->id]); $ticketB = makeTicket(['number' => '2002', 'team_id' => $teamB->id]); Livewire::actingAs($admin)->test(Queue::class) ->assertSee($ticketA->number) ->assertSee($ticketB->number); }); test('a non-admin operator gets a 403 opening a ticket outside their scope directly', function () { seedStatusesAndPriorities(); $operator = operatorUser('scoped-3@example.com'); $myTeam = Team::query()->create(['name' => 'Infrastruktura']); $otherTeam = Team::query()->create(['name' => 'Aplikacje']); $operator->teams()->attach($myTeam->id); $outOfScope = makeTicket(['number' => '3001', 'team_id' => $otherTeam->id]); Livewire::actingAs($operator)->test(TicketShow::class, ['ticket' => $outOfScope]) ->assertForbidden(); }); test('a non-admin operator can still open a ticket outside their team if it is personally assigned to them', function () { seedStatusesAndPriorities(); $operator = operatorUser('scoped-4@example.com'); $myTeam = Team::query()->create(['name' => 'Infrastruktura']); $otherTeam = Team::query()->create(['name' => 'Aplikacje']); $operator->teams()->attach($myTeam->id); $assignedElsewhere = makeTicket(['number' => '4001', 'team_id' => $otherTeam->id, 'assignee_id' => $operator->id]); Livewire::actingAs($operator)->test(TicketShow::class, ['ticket' => $assignedElsewhere]) ->assertOk(); }); test('the team reassignment dropdown on a ticket offers a non-admin operator every team, not just their own', function () { seedStatusesAndPriorities(); $operator = operatorUser('scoped-5@example.com'); $myTeam = Team::query()->create(['name' => 'Infrastruktura']); Team::query()->create(['name' => 'Aplikacje']); $operator->teams()->attach($myTeam->id); $ticket = makeTicket(['number' => '5001', 'team_id' => $myTeam->id]); $teamNames = Livewire::actingAs($operator)->test(TicketShow::class, ['ticket' => $ticket]) ->instance()->teams->pluck('name')->sort()->values()->all(); expect($teamNames)->toBe(['Aplikacje', 'Infrastruktura']); }); test('a non-admin operator can reassign a ticket to a team they do not belong to', function () { seedStatusesAndPriorities(); $operator = operatorUser('scoped-7@example.com'); $myTeam = Team::query()->create(['name' => 'Infrastruktura']); $otherTeam = Team::query()->create(['name' => 'Aplikacje']); $operator->teams()->attach($myTeam->id); $ticket = makeTicket(['number' => '5002', 'team_id' => $myTeam->id]); Livewire::actingAs($operator)->test(TicketShow::class, ['ticket' => $ticket]) ->call('setTeam', (string) $otherTeam->id) ->assertOk(); expect($ticket->fresh()->team_id)->toBe($otherTeam->id); }); test('merging cannot pull in a ticket outside the operators scope via a crafted selection', function () { seedStatusesAndPriorities(); $operator = operatorUser('scoped-6@example.com'); $myTeam = Team::query()->create(['name' => 'Infrastruktura']); $otherTeam = Team::query()->create(['name' => 'Aplikacje']); $operator->teams()->attach($myTeam->id); $mineA = makeTicket(['number' => '6001', 'team_id' => $myTeam->id]); $mineB = makeTicket(['number' => '6002', 'team_id' => $myTeam->id]); $outOfScope = makeTicket(['number' => '6003', 'team_id' => $otherTeam->id]); Livewire::actingAs($operator)->test(Queue::class) ->call('toggleSelect', $mineA->id) ->call('toggleSelect', $outOfScope->id) ->call('mergeSelected'); // only 1 ticket is actually in scope, so this must be a no-op expect(Ticket::query()->find($outOfScope->id)->status_key)->not->toBe('closed'); Livewire::actingAs($operator)->test(Queue::class) ->call('toggleSelect', $mineA->id) ->call('toggleSelect', $mineB->id) ->call('toggleSelect', $outOfScope->id) ->call('requestDeleteSelected') ->call('confirmDeleteSelected'); expect(Ticket::query()->find($mineA->id))->toBeNull() ->and(Ticket::query()->find($mineB->id))->toBeNull() ->and(Ticket::query()->find($outOfScope->id))->not->toBeNull(); });