Files
portal-api/tests/Feature/Api/Admin/Ticket/AdminTicketTest.php
baodev0 0b08689ea4 feat: harden admin auth and ticket workflows
- add rotating admin refresh tokens and password gates\n- expand ticket galleries, exports, and client resources\n- improve IP allocation auditing and dashboard data\n- enforce encrypted client access tokens on public routes
2026-10-16 17:00:55 +07:00

109 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Feature\Api\Admin\Ticket;
use App\Models\Customer;
use App\Models\Ticket;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Str;
use PhpOffice\PhpSpreadsheet\IOFactory;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\Models\Role;
use Tests\TestCase;
class AdminTicketTest extends TestCase
{
use RefreshDatabase;
private Customer $customer;
private User $admin;
protected function setUp(): void
{
parent::setUp();
$this->customer = Customer::factory()->create(['fullname' => 'Nguyễn Văn A']);
$role = Role::create(['name' => 'support', 'guard_name' => 'admin']);
foreach (['support.view', 'support.edit'] as $permission) {
$role->givePermissionTo(Permission::firstOrCreate(['name' => $permission, 'guard_name' => 'admin']));
}
$this->admin = User::factory()->create(['username' => 'hotro']);
$this->admin->assignRole($role);
}
private function makeTicket(string $status, array $attributes = []): Ticket
{
return Ticket::create(array_merge([
'uuid' => (string) Str::uuid(),
'ticket_num' => 'TKT-' . Str::upper(Str::random(6)),
'subject' => 'Subject',
'description' => 'Description',
'status' => $status,
'room' => 'technical',
'customer_id' => $this->customer->id,
], $attributes));
}
public function test_counts_only_open_and_pending_tickets(): void
{
foreach (['open', 'open', 'pending', 'resolved', 'closed'] as $status) {
$this->makeTicket($status);
}
$this->actingAs($this->admin, 'admin')
->getJson('/api/v1/administrator/tickets/pending-count')
->assertOk()
->assertJsonPath('data.count', 3);
}
public function test_detail_returns_status_history_with_causer(): void
{
$ticket = $this->makeTicket('open');
$this->actingAs($this->admin, 'admin')
->patchJson("/api/v1/administrator/tickets/{$ticket->ticket_num}/status", ['status' => 'pending'])->assertOk();
$this->actingAs($this->admin, 'admin')
->patchJson("/api/v1/administrator/tickets/{$ticket->ticket_num}/status", ['status' => 'resolved'])->assertOk();
$history = $this->actingAs($this->admin, 'admin')
->getJson("/api/v1/administrator/tickets/{$ticket->ticket_num}")
->assertOk()
->json('data.status_history');
$this->assertSame(
[['pending', 'resolved'], ['open', 'pending'], [null, 'open']],
array_map(fn ($log) => [$log['from'], $log['to']], $history),
);
$this->assertSame('hotro', $history[0]['causer_name']);
$this->assertSame('admin', $history[0]['causer_type']);
}
public function test_exports_filtered_tickets_to_branded_excel(): void
{
$this->makeTicket('open', ['subject' => 'VPS không vào được', 'ticket_num' => 'TKT-EXPORT', 'data' => json_encode(['priority' => 'high'])]);
$this->makeTicket('closed', ['subject' => 'Ticket khác']);
$response = $this->actingAs($this->admin, 'admin')
->get('/api/v1/administrator/tickets/export?' . http_build_query(['filter' => 'vps', 'status' => ['open']]))
->assertOk();
$sheet = IOFactory::load($response->baseResponse->getFile()->getPathname())->getActiveSheet();
$this->assertSame('DANH SÁCH TICKET HỖ TRỢ', $sheet->getCell('A6')->getValue());
$this->assertSame('Mã ticket', $sheet->getCell('A7')->getValue());
$this->assertSame(
['TKT-EXPORT', 'VPS không vào được', 'Nguyễn Văn A', $this->customer->email, 'Kỹ thuật', 'Cao', 'Mở', 0],
array_slice($sheet->rangeToArray('A8:J8', null, true, false)[0], 0, 8),
);
$this->assertNull($sheet->getCell('A9')->getValue());
$this->actingAs(User::factory()->create(), 'admin')
->getJson('/api/v1/administrator/tickets/export')
->assertForbidden();
}
}