This commit is contained in:
2026-07-21 23:39:19 +02:00
commit b33b217bdb
217 changed files with 32076 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
<?php
use App\Livewire\Admin\Panel;
use App\Livewire\Client\TicketShow as ClientTicketShow;
use App\Livewire\Operator\TicketShow as OperatorTicketShow;
use App\Models\Role;
use App\Models\TicketMessageAuthor;
use App\Models\User;
use App\Models\UserField;
use App\Models\UserFieldValue;
use App\Services\TicketService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Livewire\Livewire;
test('the roles table is seeded with client, operator and admin', function () {
expect(Role::query()->pluck('key')->all())->toEqualCanonicalizing(['client', 'operator', 'admin']);
});
test('a users roles are stored in the role_user pivot, not a JSON column', function () {
$user = User::query()->create(['name' => 'Multi Role', 'email' => 'multirole@example.com', 'roles' => ['operator', 'admin']]);
expect(Schema::hasColumn('users', 'roles'))->toBeFalse()
->and(DB::table('role_user')->where('user_id', $user->id)->count())->toBe(2)
->and($user->fresh()->roles)->toEqualCanonicalizing(['operator', 'admin'])
->and($user->isAdmin())->toBeTrue()
->and($user->isOperator())->toBeTrue()
->and($user->isClient())->toBeFalse();
});
test('admin toggling a users role in the panel updates role_user, not a JSON column', function () {
$admin = adminUser();
$target = User::query()->create(['name' => 'Toggle Me', 'email' => 'toggle@example.com', 'roles' => ['client']]);
Livewire::actingAs($admin)->test(Panel::class)
->call('toggleUserRole', $target->id, 'operator')
->assertOk();
expect($target->fresh()->roles)->toEqualCanonicalizing(['client', 'operator']);
});
test('a users custom field values are stored in user_field_values, not a JSON column', function () {
$field = UserField::query()->create(['label' => 'Dział', 'type' => 'text', 'sort_order' => 1]);
$user = User::query()->create([
'name' => 'Field Test', 'email' => 'fieldtest@example.com', 'roles' => ['client'],
'custom_field_values' => [$field->id => 'IT'],
]);
expect(Schema::hasColumn('users', 'custom_field_values'))->toBeFalse()
->and(UserFieldValue::query()->where('user_id', $user->id)->where('user_field_id', $field->id)->value('value'))->toBe('IT')
->and($user->fresh()->custom_field_values)->toBe([$field->id => 'IT']);
});
test('replacing a users field values drops the ones no longer present, matching the old JSON full-replace semantics', function () {
$field = UserField::query()->create(['label' => 'Dział', 'type' => 'text', 'sort_order' => 1]);
$user = User::query()->create([
'name' => 'Field Test 2', 'email' => 'fieldtest2@example.com', 'roles' => ['client'],
'custom_field_values' => [$field->id => 'IT'],
]);
$user->update(['custom_field_values' => []]);
expect(UserFieldValue::query()->where('user_id', $user->id)->exists())->toBeFalse();
});
test('a client reply links the message to its author and the client role via ticket_message_authors', function () {
seedStatusesAndPriorities();
$client = User::query()->create(['name' => 'Klient', 'email' => 'klient-msg@example.com', 'roles' => ['client']]);
$ticket = makeTicket(['customer_id' => $client->id, 'email' => $client->email, 'name' => $client->name]);
app(TicketService::class)->clientReply($ticket, $client, 'Odpowiedź klienta');
$message = $ticket->messages()->latest('id')->first();
expect(Schema::hasColumn('ticket_messages', 'author_id'))->toBeFalse()
->and(Schema::hasColumn('ticket_messages', 'role'))->toBeFalse()
->and($message->role)->toBe('client')
->and($message->author_id)->toBe($client->id)
->and($message->author->id)->toBe($client->id);
});
test('an operator reply and note link to the operator role', function () {
seedStatusesAndPriorities();
$operator = operatorUser('op-msg@example.com');
$ticket = makeTicket();
app(TicketService::class)->operatorReply($ticket, $operator, 'Odpowiedź operatora');
app(TicketService::class)->operatorNote($ticket, $operator, 'Notatka wewnętrzna');
$reply = $ticket->messages()->where('internal', false)->latest('id')->first();
$note = $ticket->messages()->where('internal', true)->latest('id')->first();
expect($reply->role)->toBe('operator')->and($reply->author_id)->toBe($operator->id)
->and($note->role)->toBe('operator')->and($note->author_id)->toBe($operator->id);
});
test('a system-generated message (merge announcement) has no author and role defaults to "system"', function () {
seedStatusesAndPriorities();
$primary = makeTicket(['number' => '2001']);
$other = makeTicket(['number' => '2002']);
app(TicketService::class)->merge([$primary->id, $other->id]);
$announcement = $primary->messages()->where('body', 'like', '%Scalono zgłoszenia%')->firstOrFail();
expect($announcement->role)->toBe('system')
->and($announcement->author_id)->toBeNull()
->and(TicketMessageAuthor::query()->where('ticket_message_id', $announcement->id)->exists())->toBeFalse();
});
test('a client can edit their own message but not an operators message on the same ticket', function () {
seedStatusesAndPriorities();
$client = User::query()->create(['name' => 'Owner', 'email' => 'owner@example.com', 'roles' => ['client']]);
$operator = operatorUser('op-on-client-ticket@example.com');
$ticket = makeTicket(['customer_id' => $client->id, 'email' => $client->email, 'name' => $client->name]);
app(TicketService::class)->clientReply($ticket, $client, 'Treść klienta');
$ownMessage = $ticket->messages()->latest('id')->first();
app(TicketService::class)->operatorReply($ticket, $operator, 'Odpowiedź operatora');
$operatorMessage = $ticket->messages()->latest('id')->first();
Livewire::actingAs($client)->test(ClientTicketShow::class, ['ticket' => $ticket])
->call('startEdit', $ownMessage->id, 'zmieniona treść')
->assertSet('editingMessageId', $ownMessage->id);
// abort_unless() inside an action call doesn't translate into a clean
// HTTP response under Livewire's test harness (it disables the app's
// exception handling for ->call()), so assert on the instance directly.
$component = Livewire::actingAs($client)->test(ClientTicketShow::class, ['ticket' => $ticket])->instance();
expect(fn () => $component->startEdit($operatorMessage->id, 'zmieniona treść'))
->toThrow(Symfony\Component\HttpKernel\Exception\HttpException::class);
});
test('an operator can edit an operator-authored message', function () {
seedStatusesAndPriorities();
$operator = operatorUser('op-edit@example.com');
$ticket = makeTicket();
app(TicketService::class)->operatorReply($ticket, $operator, 'Treść operatora');
$message = $ticket->messages()->latest('id')->first();
Livewire::actingAs($operator)->test(OperatorTicketShow::class, ['ticket' => $ticket])
->call('startEditMessage', $message->id, 'zmieniona treść')
->assertSet('editingMessageId', $message->id);
});