360 lines
11 KiB
PHP
360 lines
11 KiB
PHP
<?php
|
|
|
|
use App\Enums\TimelineStatus;
|
|
use App\Models\AdminLog;
|
|
use App\Models\Timeline;
|
|
use App\Models\User;
|
|
use Livewire\Volt\Volt;
|
|
|
|
beforeEach(function () {
|
|
$this->admin = User::factory()->admin()->create();
|
|
});
|
|
|
|
// ===========================================
|
|
// Happy Path Tests
|
|
// ===========================================
|
|
|
|
test('admin can view timeline creation form', function () {
|
|
$this->actingAs($this->admin)
|
|
->get(route('admin.timelines.create'))
|
|
->assertOk();
|
|
});
|
|
|
|
test('admin can search clients by name', function () {
|
|
$client = User::factory()->individual()->create(['full_name' => 'John Doe']);
|
|
User::factory()->individual()->create(['full_name' => 'Jane Smith']);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.timelines.create')
|
|
->set('search', 'John');
|
|
|
|
expect($component->get('clients'))->toHaveCount(1);
|
|
expect($component->get('clients')->first()->full_name)->toBe('John Doe');
|
|
});
|
|
|
|
test('admin can search clients by email', function () {
|
|
$client = User::factory()->individual()->create([
|
|
'full_name' => 'John Doe',
|
|
'email' => 'john@example.com',
|
|
]);
|
|
User::factory()->individual()->create([
|
|
'full_name' => 'Jane Smith',
|
|
'email' => 'jane@example.com',
|
|
]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.timelines.create')
|
|
->set('search', 'john@');
|
|
|
|
expect($component->get('clients'))->toHaveCount(1);
|
|
expect($component->get('clients')->first()->email)->toBe('john@example.com');
|
|
});
|
|
|
|
test('admin can create timeline with required fields', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'Test Case')
|
|
->call('create')
|
|
->assertHasNoErrors()
|
|
->assertRedirect();
|
|
|
|
expect(Timeline::where('case_name', 'Test Case')->exists())->toBeTrue();
|
|
|
|
$timeline = Timeline::where('case_name', 'Test Case')->first();
|
|
expect($timeline->user_id)->toBe($client->id);
|
|
expect($timeline->status)->toBe(TimelineStatus::Active);
|
|
expect($timeline->case_reference)->toBeNull();
|
|
});
|
|
|
|
test('admin can create timeline with case reference', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'Test Case')
|
|
->set('caseReference', 'REF-001')
|
|
->call('create')
|
|
->assertHasNoErrors();
|
|
|
|
$timeline = Timeline::where('case_name', 'Test Case')->first();
|
|
expect($timeline->case_reference)->toBe('REF-001');
|
|
});
|
|
|
|
test('initial notes creates first timeline update', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'Test Case')
|
|
->set('initialNotes', 'Initial case notes here')
|
|
->call('create')
|
|
->assertHasNoErrors();
|
|
|
|
$timeline = Timeline::where('case_name', 'Test Case')->first();
|
|
expect($timeline->updates)->toHaveCount(1);
|
|
|
|
$update = $timeline->updates->first();
|
|
expect($update->update_text)->toBe('Initial case notes here');
|
|
expect($update->admin_id)->toBe($this->admin->id);
|
|
});
|
|
|
|
test('audit log created on timeline creation', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'Test Case')
|
|
->call('create')
|
|
->assertHasNoErrors();
|
|
|
|
expect(AdminLog::where('action', 'create')
|
|
->where('target_type', 'timeline')
|
|
->where('admin_id', $this->admin->id)
|
|
->exists())->toBeTrue();
|
|
});
|
|
|
|
// ===========================================
|
|
// Validation Tests
|
|
// ===========================================
|
|
|
|
test('case name is required', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', '')
|
|
->call('create')
|
|
->assertHasErrors(['caseName' => 'required']);
|
|
});
|
|
|
|
test('case reference must be unique', function () {
|
|
$client = User::factory()->individual()->create();
|
|
Timeline::factory()->create(['case_reference' => 'REF-001']);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'Test Case')
|
|
->set('caseReference', 'REF-001')
|
|
->call('create')
|
|
->assertHasErrors(['caseReference' => 'unique']);
|
|
});
|
|
|
|
test('case reference allows multiple nulls', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
// Create a timeline without case_reference
|
|
Timeline::factory()->create([
|
|
'user_id' => $client->id,
|
|
'case_reference' => null,
|
|
]);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
// Creating another timeline without case_reference should succeed
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'Test Case')
|
|
->set('caseReference', '')
|
|
->call('create')
|
|
->assertHasNoErrors();
|
|
|
|
expect(Timeline::where('user_id', $client->id)->whereNull('case_reference')->count())->toBe(2);
|
|
});
|
|
|
|
test('client selection is required', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('caseName', 'Test Case')
|
|
->call('create')
|
|
->assertHasErrors(['selectedUserId' => 'required']);
|
|
});
|
|
|
|
test('selected client must exist', function () {
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', 99999)
|
|
->set('caseName', 'Test Case')
|
|
->call('create')
|
|
->assertHasErrors(['selectedUserId' => 'exists']);
|
|
});
|
|
|
|
// ===========================================
|
|
// Authorization Tests
|
|
// ===========================================
|
|
|
|
test('non-admin cannot access timeline creation', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($client)
|
|
->get(route('admin.timelines.create'))
|
|
->assertForbidden();
|
|
});
|
|
|
|
test('guest cannot access timeline creation', function () {
|
|
$this->get(route('admin.timelines.create'))
|
|
->assertRedirect(route('login'));
|
|
});
|
|
|
|
// ===========================================
|
|
// Edge Case Tests
|
|
// ===========================================
|
|
|
|
test('search only returns individual and company users', function () {
|
|
$individualClient = User::factory()->individual()->create(['full_name' => 'Individual Client']);
|
|
$companyClient = User::factory()->company()->create(['full_name' => 'Company Client']);
|
|
$adminUser = User::factory()->admin()->create(['full_name' => 'Admin User']);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.timelines.create')
|
|
->set('search', 'Client');
|
|
|
|
$clientNames = $component->get('clients')->pluck('full_name')->toArray();
|
|
|
|
expect($clientNames)->toContain('Individual Client');
|
|
expect($clientNames)->toContain('Company Client');
|
|
expect($clientNames)->not->toContain('Admin User');
|
|
});
|
|
|
|
test('search only returns active users', function () {
|
|
$activeClient = User::factory()->individual()->create(['full_name' => 'Active Client']);
|
|
$deactivatedClient = User::factory()->individual()->deactivated()->create(['full_name' => 'Deactivated Client']);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.timelines.create')
|
|
->set('search', 'Client');
|
|
|
|
$clientNames = $component->get('clients')->pluck('full_name')->toArray();
|
|
|
|
expect($clientNames)->toContain('Active Client');
|
|
expect($clientNames)->not->toContain('Deactivated Client');
|
|
});
|
|
|
|
test('can create multiple timelines for same client', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
// Create first timeline
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'First Case')
|
|
->call('create')
|
|
->assertHasNoErrors();
|
|
|
|
// Create second timeline for same client
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'Second Case')
|
|
->call('create')
|
|
->assertHasNoErrors();
|
|
|
|
expect(Timeline::where('user_id', $client->id)->count())->toBe(2);
|
|
});
|
|
|
|
// ===========================================
|
|
// Additional Tests
|
|
// ===========================================
|
|
|
|
test('search requires at least 2 characters', function () {
|
|
User::factory()->individual()->create(['full_name' => 'John Doe']);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.timelines.create')
|
|
->set('search', 'J');
|
|
|
|
expect($component->get('clients'))->toHaveCount(0);
|
|
|
|
$component->set('search', 'Jo');
|
|
expect($component->get('clients'))->toHaveCount(1);
|
|
});
|
|
|
|
test('selecting user sets search to user name', function () {
|
|
$client = User::factory()->individual()->create(['full_name' => 'John Doe']);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.timelines.create')
|
|
->set('search', 'John')
|
|
->call('selectUser', $client->id);
|
|
|
|
expect($component->get('selectedUserId'))->toBe($client->id);
|
|
expect($component->get('search'))->toBe('John Doe');
|
|
});
|
|
|
|
test('clearing selection resets user and search', function () {
|
|
$client = User::factory()->individual()->create(['full_name' => 'John Doe']);
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
$component = Volt::test('admin.timelines.create')
|
|
->call('selectUser', $client->id)
|
|
->call('clearSelection');
|
|
|
|
expect($component->get('selectedUserId'))->toBeNull();
|
|
expect($component->get('selectedUser'))->toBeNull();
|
|
expect($component->get('search'))->toBe('');
|
|
});
|
|
|
|
test('timeline without initial notes has no updates', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'Test Case')
|
|
->set('initialNotes', '')
|
|
->call('create')
|
|
->assertHasNoErrors();
|
|
|
|
$timeline = Timeline::where('case_name', 'Test Case')->first();
|
|
expect($timeline->updates)->toHaveCount(0);
|
|
});
|
|
|
|
test('timeline status defaults to active', function () {
|
|
$client = User::factory()->individual()->create();
|
|
|
|
$this->actingAs($this->admin);
|
|
|
|
Volt::test('admin.timelines.create')
|
|
->set('selectedUserId', $client->id)
|
|
->call('selectUser', $client->id)
|
|
->set('caseName', 'Test Case')
|
|
->call('create')
|
|
->assertHasNoErrors();
|
|
|
|
$timeline = Timeline::where('case_name', 'Test Case')->first();
|
|
expect($timeline->status)->toBe(TimelineStatus::Active);
|
|
});
|