Add feature tests for Doctor-related functionalities

This commit is contained in:
2026-09-21 05:41:06 +03:30
parent e987a528ef
commit 6e92ebaada
29 changed files with 2634 additions and 29 deletions
@@ -0,0 +1,112 @@
<?php
namespace Tests\Feature\Http\Client\Doctor;
use App\Enums\DocumentStatusEnum;
use App\Enums\InvoiceStatusEnum;
use App\Enums\UserEnum;
use App\Models\Document;
use App\Models\DocumentAssignment;
use App\Models\Invoice;
use App\Models\User;
use App\Models\Wallet;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class CaseControllerTest extends TestCase
{
use RefreshDatabase;
protected function doctor(): User
{
return User::factory()->create(['level' => UserEnum::Doctor->value]);
}
protected function assign(Document $document, User $doctor): void
{
DocumentAssignment::factory()->create(['document_id' => $document->id, 'user_id' => $doctor->id]);
}
public function test_routes_require_authentication(): void
{
$document = Document::factory()->create();
$this->get(route('dashboard.cases.index'))->assertRedirect(route('login'));
$this->get(route('dashboard.cases.show', $document))->assertRedirect(route('login'));
$this->post(route('dashboard.cases.reject', $document))->assertRedirect(route('login'));
}
public function test_index_paginates_pending_cases_of_user(): void
{
$user = User::factory()->create();
Document::factory()->count(12)->registered()->create(['requester_id' => $user->id]);
Document::factory()->completed()->create(['requester_id' => $user->id]);
$this->actingAs($user)->get(route('dashboard.cases.index'))
->assertOk()
->assertInertia(fn (Assert $page) => $page->component('Dashboard/Cases/Index')->has('cases.data', 10)->where('cases.meta.total', 12));
}
public function test_show_renders_case_for_requester_without_report_permission(): void
{
$document = Document::factory()->inProgress()->create();
$this->actingAs($document->requester)->get(route('dashboard.cases.show', $document))
->assertOk()
->assertInertia(fn (Assert $page) => $page->component('Dashboard/Cases/show')->where('case.id', $document->id)->where('canReport', false));
}
public function test_show_allows_assigned_doctor_to_report(): void
{
$doctor = $this->doctor();
$document = Document::factory()->inProgress()->create();
$this->assign($document, $doctor);
$this->actingAs($doctor)->get(route('dashboard.cases.show', $document))
->assertOk()
->assertInertia(fn (Assert $page) => $page->where('canReport', true));
}
public function test_show_forbids_unrelated_user(): void
{
$document = Document::factory()->create();
$this->actingAs(User::factory()->create())->get(route('dashboard.cases.show', $document))->assertForbidden();
}
public function test_reject_by_assigned_doctor_rejects_case_and_refunds(): void
{
$doctor = $this->doctor();
$document = Document::factory()->inProgress()->create();
$this->assign($document, $doctor);
$wallet = Wallet::factory()->create(['user_id' => $document->requester_id, 'balance' => 0]);
Invoice::factory()->paid()->create(['document_id' => $document->id, 'user_id' => $document->requester_id, 'amount' => 300000]);
$this->actingAs($doctor)->post(route('dashboard.cases.reject', $document), ['reason' => 'ناخوانا'])
->assertRedirect(route('dashboard.cases.index'))
->assertSessionHas('success');
$this->assertSame(DocumentStatusEnum::Rejected, $document->fresh()->status);
$this->assertSame(300000, $wallet->fresh()->balance);
$this->assertDatabaseHas('invoices', ['document_id' => $document->id, 'status' => InvoiceStatusEnum::Canceled->value]);
}
public function test_reject_validates_reason(): void
{
$doctor = $this->doctor();
$document = Document::factory()->inProgress()->create();
$this->assign($document, $doctor);
$this->actingAs($doctor)->post(route('dashboard.cases.reject', $document), [])->assertSessionHasErrors('reason');
}
public function test_reject_by_unassigned_user_returns_validation_error(): void
{
$document = Document::factory()->inProgress()->create();
$this->actingAs($this->doctor())->post(route('dashboard.cases.reject', $document), ['reason' => 'x'])->assertSessionHasErrors('document');
$this->assertSame(DocumentStatusEnum::InProgress, $document->fresh()->status);
}
}
@@ -0,0 +1,51 @@
<?php
namespace Tests\Feature\Http\Client\Doctor;
use App\Enums\UserEnum;
use App\Models\Document;
use App\Models\DocumentAssignment;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class DashboardControllerTest extends TestCase
{
use RefreshDatabase;
public function test_index_requires_authentication(): void
{
$this->get(route('dashboard.index'))->assertRedirect(route('login'));
}
public function test_index_renders_requester_pending_cases_and_stats(): void
{
$user = User::factory()->create(['level' => UserEnum::Vet->value]);
Document::factory()->count(7)->registered()->create(['requester_id' => $user->id]);
Document::factory()->registered()->create();
$this->actingAs($user)->get(route('dashboard.index'))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('Dashboard/Dashboard')
->has('cases.data', 5)
->where('stats.pending_cases', 7)
->where('stats.is_doctor', false));
}
public function test_index_renders_doctor_assigned_cases(): void
{
$doctor = User::factory()->create(['level' => UserEnum::Doctor->value]);
$document = Document::factory()->inProgress()->create();
DocumentAssignment::factory()->create(['document_id' => $document->id, 'user_id' => $doctor->id]);
Document::factory()->inProgress()->create();
$this->actingAs($doctor)->get(route('dashboard.index'))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->has('cases.data', 1)
->where('cases.data.0.id', $document->id)
->where('stats.is_doctor', true));
}
}
@@ -0,0 +1,38 @@
<?php
namespace Tests\Feature\Http\Client\Doctor;
use App\Models\Document;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class DocumentControllerTest extends TestCase
{
use RefreshDatabase;
public function test_routes_require_authentication(): void
{
$this->get(route('dashboard.documents.index'))->assertRedirect(route('login'));
}
public function test_index_lists_pending_documents_of_user(): void
{
$user = User::factory()->create();
Document::factory()->count(3)->registered()->create(['requester_id' => $user->id]);
Document::factory()->registered()->create();
$this->actingAs($user)->get(route('dashboard.documents.index'))
->assertOk()
->assertInertia(fn (Assert $page) => $page->component('Dashboard/Documents/Index')->has('documents.data', 3));
}
public function test_show_redirects_to_case_page(): void
{
$document = Document::factory()->create();
$this->actingAs($document->requester)->get(route('dashboard.documents.show', $document))
->assertRedirect(route('dashboard.cases.show', $document));
}
}
@@ -0,0 +1,97 @@
<?php
namespace Tests\Feature\Http\Client\Doctor;
use App\Enums\DocumentStatusEnum;
use App\Enums\UserEnum;
use App\Models\Document;
use App\Models\DocumentAssignment;
use App\Models\Report;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class ReportControllerTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
Queue::fake();
Storage::fake('public');
}
protected function doctor(): User
{
return User::factory()->create(['level' => UserEnum::Doctor->value]);
}
public function test_routes_require_authentication(): void
{
$this->get(route('dashboard.reports.index'))->assertRedirect(route('login'));
$this->post(route('dashboard.reports.store'))->assertRedirect(route('login'));
}
public function test_index_shows_reports_written_by_doctor_and_pending_cases(): void
{
$doctor = $this->doctor();
Report::factory()->count(2)->create(['doctor_id' => $doctor->id]);
Report::factory()->create();
$pending = Document::factory()->inProgress()->create();
DocumentAssignment::factory()->create(['document_id' => $pending->id, 'user_id' => $doctor->id]);
$this->actingAs($doctor)->get(route('dashboard.reports.index'))
->assertOk()
->assertInertia(fn (Assert $page) => $page->component('Dashboard/Reports/Index')->has('reports.data', 2)->has('pendingCases.data', 1));
}
public function test_index_shows_reports_of_requesters_documents_without_pending_cases(): void
{
$requester = User::factory()->create(['level' => UserEnum::Vet->value]);
$document = Document::factory()->create(['requester_id' => $requester->id]);
Report::factory()->create(['document_id' => $document->id]);
Report::factory()->create();
$this->actingAs($requester)->get(route('dashboard.reports.index'))
->assertOk()
->assertInertia(fn (Assert $page) => $page->has('reports.data', 1)->where('pendingCases', null));
}
public function test_store_submits_report_and_completes_document(): void
{
$doctor = $this->doctor();
$document = Document::factory()->inProgress()->create();
DocumentAssignment::factory()->create(['document_id' => $document->id, 'user_id' => $doctor->id]);
$this->actingAs($doctor)->post(route('dashboard.reports.store'), [
'document_id' => $document->id,
'finding' => 'یافته',
'files' => [UploadedFile::fake()->image('a.jpg')],
])->assertRedirect(route('dashboard.cases.index'))->assertSessionHas('success');
$this->assertDatabaseHas('reports', ['document_id' => $document->id, 'doctor_id' => $doctor->id, 'finding' => 'یافته']);
$this->assertSame(DocumentStatusEnum::Completed, $document->fresh()->status);
$this->assertDatabaseCount('media', 1);
}
public function test_store_validates_input(): void
{
$this->actingAs($this->doctor())->post(route('dashboard.reports.store'), [])->assertSessionHasErrors(['document_id', 'finding']);
}
public function test_store_rejects_unassigned_doctor(): void
{
$document = Document::factory()->inProgress()->create();
$this->actingAs($this->doctor())->post(route('dashboard.reports.store'), ['document_id' => $document->id, 'finding' => 'x'])
->assertSessionHasErrors('document');
$this->assertDatabaseCount('reports', 0);
}
}
@@ -0,0 +1,99 @@
<?php
namespace Tests\Feature\Http\Client\Doctor;
use App\Enums\ShiftDayEnum;
use App\Enums\UserEnum;
use App\Models\Shift;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Carbon;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
class ShiftControllerTest extends TestCase
{
use RefreshDatabase;
protected function doctor(): User
{
return User::factory()->create(['level' => UserEnum::Doctor->value]);
}
public function test_routes_require_authentication(): void
{
$shift = Shift::factory()->create();
$this->get(route('dashboard.shifts.index'))->assertRedirect(route('login'));
$this->put(route('dashboard.shifts.update', $shift))->assertRedirect(route('login'));
$this->delete(route('dashboard.shifts.destroy', $shift))->assertRedirect(route('login'));
}
public function test_routes_are_forbidden_for_non_doctors(): void
{
$vet = User::factory()->create(['level' => UserEnum::Vet->value]);
$shift = Shift::factory()->create();
$this->actingAs($vet)->get(route('dashboard.shifts.index'))->assertForbidden();
$this->actingAs($vet)->put(route('dashboard.shifts.update', $shift))->assertForbidden();
$this->actingAs($vet)->delete(route('dashboard.shifts.destroy', $shift))->assertForbidden();
}
public function test_index_lists_my_shifts_and_open_upcoming_shifts_sorted(): void
{
Carbon::setTestNow('2026-09-21 10:00:00');
$doctor = $this->doctor();
$mine = Shift::factory()->weekly(ShiftDayEnum::Monday)->forDoctor($doctor)->create();
Shift::factory()->weekly(ShiftDayEnum::Monday)->forDoctor()->create();
$openLater = Shift::factory()->onDate('2026-09-25')->create();
$openWeekly = Shift::factory()->weekly(ShiftDayEnum::Saturday)->create();
Shift::factory()->onDate('2026-09-20')->create();
Shift::factory()->weekly(ShiftDayEnum::Sunday)->inactive()->create();
$this->actingAs($doctor)->get(route('dashboard.shifts.index'))
->assertOk()
->assertInertia(fn (Assert $page) => $page
->component('Dashboard/Shifts/Index')
->has('myShifts.data', 1)
->where('myShifts.data.0.id', $mine->id)
->has('openShifts.data', 2)
->where('openShifts.data.0.id', $openWeekly->id)
->where('openShifts.data.1.id', $openLater->id));
}
public function test_update_claims_open_shift(): void
{
$doctor = $this->doctor();
$shift = Shift::factory()->onDate('2026-09-21')->create();
$this->actingAs($doctor)->put(route('dashboard.shifts.update', $shift))->assertOk();
$this->assertDatabaseHas('shifts', ['id' => $shift->id, 'user_id' => $doctor->id]);
}
public function test_update_rejects_taken_shift(): void
{
$shift = Shift::factory()->onDate('2026-09-21')->forDoctor()->create();
$this->actingAs($this->doctor())->putJson(route('dashboard.shifts.update', $shift))->assertUnprocessable()->assertJsonValidationErrors('shift');
}
public function test_destroy_releases_own_shift(): void
{
$doctor = $this->doctor();
$shift = Shift::factory()->onDate('2026-09-21')->forDoctor($doctor)->create();
$this->actingAs($doctor)->delete(route('dashboard.shifts.destroy', $shift))->assertOk();
$this->assertDatabaseHas('shifts', ['id' => $shift->id, 'user_id' => null]);
}
public function test_destroy_rejects_another_doctors_shift(): void
{
$shift = Shift::factory()->onDate('2026-09-21')->forDoctor()->create();
$this->actingAs($this->doctor())->deleteJson(route('dashboard.shifts.destroy', $shift))->assertUnprocessable();
$this->assertNotNull($shift->fresh()->user_id);
}
}