diff --git a/database/factories/CourseFactory.php b/database/factories/CourseFactory.php new file mode 100644 index 0000000..da832c1 --- /dev/null +++ b/database/factories/CourseFactory.php @@ -0,0 +1,27 @@ + + */ +class CourseFactory extends Factory +{ + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'code' => strtoupper(fake()->unique()->bothify('??# ###')), + 'name' => fake()->words(3, true), + 'credit' => fake()->numberBetween(1, 6), + 'semester' => fake()->numberBetween(1, 8), + 'lecturer' => fake()->name(), + ]; + } +} diff --git a/tests/Feature/CourseTest.php b/tests/Feature/CourseTest.php new file mode 100644 index 0000000..305a173 --- /dev/null +++ b/tests/Feature/CourseTest.php @@ -0,0 +1,233 @@ +value); + Role::findOrCreate(RoleEnum::Kosma->value); + Role::findOrCreate(RoleEnum::Student->value); + + $permissions = [ + 'ViewAny:Course', + 'Create:Course', + 'Update:Course', + 'Delete:Course', + 'Restore:Course', + 'ForceDelete:Course', + 'RestoreAny:Course', + 'ForceDeleteAny:Course', + ]; + + foreach ($permissions as $permission) { + Permission::findOrCreate($permission); + } +}); + +describe('Course Authorization', function () { + it('restricts actions to Developer and Kosma only, while allowing all to view', function () { + $developer = User::factory()->create(); + $developer->assignRole(RoleEnum::Developer); + $developer->givePermissionTo(['ViewAny:Course', 'Create:Course']); + + $kosma = User::factory()->create(); + $kosma->assignRole(RoleEnum::Kosma); + $kosma->givePermissionTo(['ViewAny:Course', 'Create:Course']); + + $regularStudent = User::factory()->create(); + $regularStudent->assignRole(RoleEnum::Student); + $regularStudent->givePermissionTo('ViewAny:Course'); + + // All can view the list + $this->actingAs($developer); + Livewire::test(ManageCourses::class)->assertSuccessful(); + + $this->actingAs($kosma); + Livewire::test(ManageCourses::class)->assertSuccessful(); + + $this->actingAs($regularStudent); + Livewire::test(ManageCourses::class)->assertSuccessful(); + + // Only Developer and Kosma can see the Create action + $this->actingAs($developer); + Livewire::test(ManageCourses::class)->assertActionVisible('createCourse'); + + $this->actingAs($kosma); + Livewire::test(ManageCourses::class)->assertActionVisible('createCourse'); + + $this->actingAs($regularStudent); + Livewire::test(ManageCourses::class)->assertActionHidden('createCourse'); + }); +}); + +describe('Course Model', function () { + it('supports Soft Deletes', function () { + $course = Course::factory()->create(); + $course->delete(); + + expect($course->trashed())->toBeTrue(); + expect(Course::find($course->id))->toBeNull(); + }); + + it('has necessary relationships', function () { + $course = Course::factory()->create(); + + expect($course->assignments())->toBeInstanceOf(HasMany::class) + ->and($course->materials())->toBeInstanceOf(HasMany::class) + ->and($course->studyGroups())->toBeInstanceOf(BelongsToMany::class); + }); +}); + +describe('Course CRUD in Filament', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Course', 'Create:Course', 'Update:Course', 'Delete:Course']); + $this->actingAs($user); + }); + + it('can list courses', function () { + $courses = Course::factory()->count(3)->create(); + Livewire::test(ManageCourses::class) + ->assertCanSeeTableRecords($courses); + }); + + it('can create a new course', function () { + Livewire::test(ManageCourses::class) + ->callAction('createCourse', [ + 'code' => 'CS101', + 'name' => 'Intro to Programming', + 'credit' => 3, + 'semester' => 1, + 'lecturer' => 'Dr. Smith', + ]) + ->assertHasNoActionErrors(); + + $this->assertDatabaseHas('courses', [ + 'code' => 'CS101', + 'name' => 'Intro to Programming', + ]); + }); + + it('can update course data', function () { + $course = Course::factory()->create(['name' => 'Old Course Name']); + + Livewire::test(ManageCourses::class) + ->callTableAction('editCourse', $course, [ + 'name' => 'Updated Course Name', + ]) + ->assertHasNoTableActionErrors(); + + expect($course->refresh()->name)->toBe('Updated Course Name'); + }); + + it('can delete a course', function () { + $course = Course::factory()->create(); + + Livewire::test(ManageCourses::class) + ->callTableAction('delete', $course); + + expect(Course::find($course->id))->toBeNull(); + expect(Course::withTrashed()->find($course->id))->not->toBeNull(); + }); +}); + +describe('Course Specific Features', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Course', 'Restore:Course', 'RestoreAny:Course']); + $this->actingAs($user); + }); + + it('can restore a deleted course', function () { + $course = Course::factory()->create(); + $course->delete(); + + Livewire::test(ManageCourses::class) + ->filterTable('trashed', 'only') + ->callTableAction('restore', $course); + + expect($course->refresh()->trashed())->toBeFalse(); + }); + + it('can perform bulk actions (Delete & Restore)', function () { + $courses = Course::factory()->count(3)->create(); + + // Bulk Delete + Livewire::test(ManageCourses::class) + ->callTableBulkAction('delete', $courses); + + foreach ($courses as $course) { + expect($course->refresh()->trashed())->toBeTrue(); + } + + // Bulk Restore + Livewire::test(ManageCourses::class) + ->filterTable('trashed', 'only') + ->callTableBulkAction('restore', $courses); + + foreach ($courses as $course) { + expect($course->refresh()->trashed())->toBeFalse(); + } + }); +}); + +describe('Search, Sorting, and Filters', function () { + beforeEach(function () { + $user = User::factory()->create(); + $user->assignRole(RoleEnum::Developer); + $user->givePermissionTo(['ViewAny:Course']); + $this->actingAs($user); + }); + + it('can search courses by name or code', function () { + $target = Course::factory()->create([ + 'name' => 'Target Course', + 'code' => 'TARGET123', + ]); + $other = Course::factory()->create([ + 'name' => 'Other Course', + 'code' => 'OTHER456', + ]); + + Livewire::test(ManageCourses::class) + ->searchTable('Target Course') + ->assertCanSeeTableRecords([$target]) + ->assertCanNotSeeTableRecords([$other]) + ->searchTable('TARGET123') + ->assertCanSeeTableRecords([$target]); + }); + + it('can filter courses by trashed status', function () { + Course::query()->forceDelete(); + $trashed = Course::factory()->create(['name' => 'Trashed Course', 'semester' => 1]); + $trashed->delete(); + + Livewire::test(ManageCourses::class) + ->filterTable('trashed', 'only') + ->assertSee('Trashed Course'); + }); + + it('can sort courses by name', function () { + Course::query()->forceDelete(); + + $courseA = Course::factory()->create(['name' => 'A Course', 'semester' => 1]); + $courseB = Course::factory()->create(['name' => 'B Course', 'semester' => 1]); + $courseC = Course::factory()->create(['name' => 'C Course', 'semester' => 1]); + + Livewire::test(ManageCourses::class) + ->sortTable('name', 'asc') + ->assertCanSeeTableRecords([$courseA, $courseB, $courseC], inOrder: true); + }); +});