From 0d16d8f5124698ffd93ef80ac8b98c2c887bbfad Mon Sep 17 00:00:00 2001 From: mehrab esmailnia Date: Mon, 21 Sep 2026 05:47:44 +0330 Subject: [PATCH] feat: add comprehensive tests for Medium, OtpCode, Shift models and various Enums --- docs/TESTING.md | 17 +++-- tests/Feature/Models/MediumTest.php | 76 +++++++++++++++++++ tests/Feature/Models/OtpCodeTest.php | 31 ++++++++ tests/Feature/Models/ShiftTest.php | 38 ++++++++++ tests/Unit/Enums/DocumentModalityEnumTest.php | 30 ++++++++ tests/Unit/Enums/DocumentPriorityEnumTest.php | 20 +++++ tests/Unit/Enums/EnumHasOptionsTest.php | 42 ++++++++++ tests/Unit/Enums/ShiftDayEnumTest.php | 43 +++++++++++ 8 files changed, 289 insertions(+), 8 deletions(-) create mode 100644 tests/Feature/Models/MediumTest.php create mode 100644 tests/Feature/Models/OtpCodeTest.php create mode 100644 tests/Feature/Models/ShiftTest.php create mode 100644 tests/Unit/Enums/DocumentModalityEnumTest.php create mode 100644 tests/Unit/Enums/DocumentPriorityEnumTest.php create mode 100644 tests/Unit/Enums/EnumHasOptionsTest.php create mode 100644 tests/Unit/Enums/ShiftDayEnumTest.php diff --git a/docs/TESTING.md b/docs/TESTING.md index 288d21a..18f5fc0 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -32,7 +32,8 @@ app/Enums/ShiftDayEnum.php → tests/Unit/Enums/ShiftDayEnum app/Models/Shift.php → tests/Feature/Models/ShiftTest.php ``` -- `tests/Unit/` — no database, no facades. Enums only. +- `tests/Unit/` — no database. Enums only; extend `PHPUnit\Framework\TestCase` unless a framework helper is needed + (`EnumHasOptionsTest` extends `Tests\TestCase` because `options()` calls `__()`). - `tests/Feature/` — boots the app, uses `RefreshDatabase` (sqlite `:memory:`, already configured in `phpunit.xml`). - The default `ExampleTest` placeholders have been removed; never re-add them. @@ -178,19 +179,19 @@ Legend: `☐` not written · `☑` written · `—` intentionally not tested | Priority | Source | Test | Status | |----------|--------|------|--------| -| P3 | `app/Enums/ShiftDayEnum.php` | `tests/Unit/Enums/ShiftDayEnumTest.php` | ☐ | -| P3 | `app/Enums/DocumentModalityEnum.php` | `tests/Unit/Enums/DocumentModalityEnumTest.php` | ☐ | -| P3 | `app/Enums/DocumentPriorityEnum.php` | `tests/Unit/Enums/DocumentPriorityEnumTest.php` | ☐ | -| P3 | `app/Enums/EnumHasOptions.php` (via any enum using it) | `tests/Unit/Enums/EnumHasOptionsTest.php` | ☐ | +| P3 | `app/Enums/ShiftDayEnum.php` | `tests/Unit/Enums/ShiftDayEnumTest.php` | ☑ | +| P3 | `app/Enums/DocumentModalityEnum.php` | `tests/Unit/Enums/DocumentModalityEnumTest.php` | ☑ | +| P3 | `app/Enums/DocumentPriorityEnum.php` | `tests/Unit/Enums/DocumentPriorityEnumTest.php` | ☑ | +| P3 | `app/Enums/EnumHasOptions.php` (via any enum using it) | `tests/Unit/Enums/EnumHasOptionsTest.php` | ☑ | | — | all other enums (values only) | | — | ### Models | Priority | Source | Test | Status | |----------|--------|------|--------| -| P3 | `app/Models/Shift.php` (`scopeActive`, `scopeOpen`) | `tests/Feature/Models/ShiftTest.php` | ☐ | -| P3 | `app/Models/Medium.php` (`fullUrl`, `humanSize`, `exists`, `isPublic`, `isPrivate`, `deleteFile`) | `tests/Feature/Models/MediumTest.php` | ☐ | -| P3 | `app/Models/OtpCode.php` (`isExpired`) | `tests/Feature/Models/OtpCodeTest.php` | ☐ | +| P3 | `app/Models/Shift.php` (`scopeActive`, `scopeOpen`) | `tests/Feature/Models/ShiftTest.php` | ☑ | +| P3 | `app/Models/Medium.php` (`fullUrl`, `humanSize`, `exists`, `isPublic`, `isPrivate`, `deleteFile`) | `tests/Feature/Models/MediumTest.php` | ☑ | +| P3 | `app/Models/OtpCode.php` (`isExpired`) | `tests/Feature/Models/OtpCodeTest.php` | ☑ | | — | all other models (relations / fillable only) | | — | ### Factories diff --git a/tests/Feature/Models/MediumTest.php b/tests/Feature/Models/MediumTest.php new file mode 100644 index 0000000..352a9a0 --- /dev/null +++ b/tests/Feature/Models/MediumTest.php @@ -0,0 +1,76 @@ +public()->create(); + $private = Medium::factory()->private()->create(); + + $this->assertTrue($public->isPublic()); + $this->assertFalse($public->isPrivate()); + $this->assertTrue($private->isPrivate()); + $this->assertFalse($private->isPublic()); + } + + public function test_get_storage_disk_matches_medium_disk(): void + { + $private = Medium::factory()->private()->create(['path' => 'a.txt']); + Storage::disk('private')->put('a.txt', 'x'); + + $this->assertTrue($private->getStorageDisk()->exists('a.txt')); + $this->assertFalse(Storage::disk('public')->exists('a.txt')); + } + + public function test_full_url_returns_absolute_path_on_matching_disk(): void + { + $public = Medium::factory()->public()->create(['path' => 'files/a.png']); + $private = Medium::factory()->private()->create(['path' => 'files/b.png']); + + $this->assertSame(Storage::disk('public')->path('files/a.png'), $public->full_url); + $this->assertSame(Storage::disk('private')->path('files/b.png'), $private->full_url); + } + + public function test_exists_get_contents_and_delete_file(): void + { + $medium = Medium::factory()->private()->create(['path' => 'files/c.txt']); + Storage::disk('private')->put('files/c.txt', 'hello'); + + $this->assertTrue($medium->exists()); + $this->assertSame('hello', $medium->getContents()); + $this->assertTrue($medium->deleteFile()); + $this->assertFalse($medium->exists()); + $this->assertFalse($medium->deleteFile()); + } + + public function test_human_size_formats_bytes_into_units(): void + { + $this->assertSame('512 B', Medium::factory()->make(['size' => 512])->human_size); + $this->assertSame('1 KB', Medium::factory()->make(['size' => 1025])->human_size); + $this->assertSame('1.5 MB', Medium::factory()->make(['size' => 1572864])->human_size); + $this->assertSame('2 GB', Medium::factory()->make(['size' => 2147483648])->human_size); + } + + public function test_disk_is_cast_to_enum(): void + { + $this->assertSame(MediaDiskEnum::Private, Medium::factory()->private()->create()->fresh()->disk); + } +} diff --git a/tests/Feature/Models/OtpCodeTest.php b/tests/Feature/Models/OtpCodeTest.php new file mode 100644 index 0000000..9abb38b --- /dev/null +++ b/tests/Feature/Models/OtpCodeTest.php @@ -0,0 +1,31 @@ +assertFalse(OtpCode::factory()->create()->isExpired()); + } + + public function test_is_expired_is_true_after_expiry(): void + { + $this->assertTrue(OtpCode::factory()->expired()->create()->isExpired()); + } + + public function test_is_expired_is_false_at_exact_expiry_moment(): void + { + Carbon::setTestNow('2026-09-21 10:00:00'); + $otp = OtpCode::factory()->create(['expires_at' => now()]); + + $this->assertFalse($otp->isExpired()); + } +} diff --git a/tests/Feature/Models/ShiftTest.php b/tests/Feature/Models/ShiftTest.php new file mode 100644 index 0000000..7339559 --- /dev/null +++ b/tests/Feature/Models/ShiftTest.php @@ -0,0 +1,38 @@ +create(); + Shift::factory()->inactive()->create(); + + $this->assertSame([$active->id], Shift::active()->pluck('id')->all()); + } + + public function test_open_scope_returns_only_unassigned_shifts(): void + { + $open = Shift::factory()->create(); + Shift::factory()->forDoctor()->create(); + + $this->assertSame([$open->id], Shift::open()->pluck('id')->all()); + } + + public function test_scopes_can_be_chained(): void + { + $target = Shift::factory()->create(); + Shift::factory()->inactive()->create(); + Shift::factory()->forDoctor()->create(); + Shift::factory()->forDoctor()->inactive()->create(); + + $this->assertSame([$target->id], Shift::active()->open()->pluck('id')->all()); + } +} diff --git a/tests/Unit/Enums/DocumentModalityEnumTest.php b/tests/Unit/Enums/DocumentModalityEnumTest.php new file mode 100644 index 0000000..151c47d --- /dev/null +++ b/tests/Unit/Enums/DocumentModalityEnumTest.php @@ -0,0 +1,30 @@ +assertSame(1000000, DocumentModalityEnum::CtScan->price()); + $this->assertSame(1200000, DocumentModalityEnum::Mri->price()); + $this->assertSame(500000, DocumentModalityEnum::Sonography->price()); + $this->assertSame(700000, DocumentModalityEnum::Echo->price()); + $this->assertSame(400000, DocumentModalityEnum::Radiology->price()); + } + + public function test_every_case_has_a_positive_price(): void + { + foreach (DocumentModalityEnum::cases() as $modality) { + $this->assertGreaterThan(0, $modality->price(), $modality->name); + } + } + + public function test_values_are_slugs_used_by_forms(): void + { + $this->assertSame(['ct-scan', 'mri', 'sonography', 'echo', 'radiology'], DocumentModalityEnum::values()); + } +} diff --git a/tests/Unit/Enums/DocumentPriorityEnumTest.php b/tests/Unit/Enums/DocumentPriorityEnumTest.php new file mode 100644 index 0000000..08a7c3a --- /dev/null +++ b/tests/Unit/Enums/DocumentPriorityEnumTest.php @@ -0,0 +1,20 @@ +assertSame(24, DocumentPriorityEnum::Medium->processingHours()); + $this->assertSame(3, DocumentPriorityEnum::High->processingHours()); + } + + public function test_high_priority_is_processed_faster_than_medium(): void + { + $this->assertLessThan(DocumentPriorityEnum::Medium->processingHours(), DocumentPriorityEnum::High->processingHours()); + } +} diff --git a/tests/Unit/Enums/EnumHasOptionsTest.php b/tests/Unit/Enums/EnumHasOptionsTest.php new file mode 100644 index 0000000..8531744 --- /dev/null +++ b/tests/Unit/Enums/EnumHasOptionsTest.php @@ -0,0 +1,42 @@ +assertSame(['عادی', 'اورژانسی'], DocumentPriorityEnum::values()); + } + + public function test_keys_returns_case_names_in_declaration_order(): void + { + $this->assertSame(['Medium', 'High'], DocumentPriorityEnum::keys()); + } + + public function test_from_name_resolves_case_by_name(): void + { + $this->assertSame(DocumentPriorityEnum::High, DocumentPriorityEnum::fromName('High')); + $this->assertSame(ShiftDayEnum::Friday, ShiftDayEnum::fromName('Friday')); + } + + public function test_from_name_returns_null_for_unknown_or_value_input(): void + { + $this->assertNull(DocumentPriorityEnum::fromName('Urgent')); + $this->assertNull(DocumentPriorityEnum::fromName(DocumentPriorityEnum::High->value)); + $this->assertNull(DocumentPriorityEnum::fromName('high')); + } + + public function test_options_is_keyed_by_value_with_translation_per_case(): void + { + $options = DocumentPriorityEnum::options(); + + $this->assertSame(['عادی', 'اورژانسی'], array_keys($options)); + $this->assertSame(__('enums.DocumentPriorityEnum.Medium'), $options['عادی']); + $this->assertSame(__('enums.DocumentPriorityEnum.High'), $options['اورژانسی']); + } +} diff --git a/tests/Unit/Enums/ShiftDayEnumTest.php b/tests/Unit/Enums/ShiftDayEnumTest.php new file mode 100644 index 0000000..30e2acb --- /dev/null +++ b/tests/Unit/Enums/ShiftDayEnumTest.php @@ -0,0 +1,43 @@ +assertSame(Carbon::SATURDAY, ShiftDayEnum::Saturday->carbonDay()); + $this->assertSame(Carbon::SUNDAY, ShiftDayEnum::Sunday->carbonDay()); + $this->assertSame(Carbon::MONDAY, ShiftDayEnum::Monday->carbonDay()); + $this->assertSame(Carbon::TUESDAY, ShiftDayEnum::Tuesday->carbonDay()); + $this->assertSame(Carbon::WEDNESDAY, ShiftDayEnum::Wednesday->carbonDay()); + $this->assertSame(Carbon::THURSDAY, ShiftDayEnum::Thursday->carbonDay()); + $this->assertSame(Carbon::FRIDAY, ShiftDayEnum::Friday->carbonDay()); + } + + public function test_from_date_resolves_weekday_of_given_date(): void + { + $this->assertSame(ShiftDayEnum::Saturday, ShiftDayEnum::fromDate(Carbon::parse('2026-09-26'))); + $this->assertSame(ShiftDayEnum::Sunday, ShiftDayEnum::fromDate(Carbon::parse('2026-09-20'))); + $this->assertSame(ShiftDayEnum::Monday, ShiftDayEnum::fromDate(Carbon::parse('2026-09-21'))); + $this->assertSame(ShiftDayEnum::Friday, ShiftDayEnum::fromDate(Carbon::parse('2026-09-25'))); + } + + public function test_from_date_and_carbon_day_round_trip_for_every_case(): void + { + foreach (ShiftDayEnum::cases() as $day) { + $date = Carbon::parse('2026-09-21')->next($day->carbonDay()); + + $this->assertSame($day, ShiftDayEnum::fromDate($date)); + } + } + + public function test_values_follow_persian_week_order_starting_saturday(): void + { + $this->assertSame(['شنبه', 'یکشنبه', 'دوشنبه', 'سه‌شنبه', 'چهارشنبه', 'پنج‌شنبه', 'جمعه'], ShiftDayEnum::values()); + } +}