diff --git a/app/Http/Controllers/CRUDController.php b/app/Http/Controllers/CRUDController.php index 3eef05b5..254ef017 100644 --- a/app/Http/Controllers/CRUDController.php +++ b/app/Http/Controllers/CRUDController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; +use Illuminate\Support\Str; use Inertia\Inertia; /** @@ -36,6 +37,14 @@ abstract class CRUDController extends Controller */ protected array $search = []; + public function __construct() + { + $basename = Str::replaceEnd('CRUDController', '', class_basename($this)); + + $this->model ??= "App\\Models\\{$basename}"; + $this->view ??= $basename; + } + /** * The validation rules for the store method. * diff --git a/app/Http/Controllers/DepartmentCRUDController.php b/app/Http/Controllers/DepartmentCRUDController.php index 1013868f..53807b17 100644 --- a/app/Http/Controllers/DepartmentCRUDController.php +++ b/app/Http/Controllers/DepartmentCRUDController.php @@ -2,15 +2,10 @@ namespace App\Http\Controllers; -use App\Models\Department; use App\Models\Edition; class DepartmentCRUDController extends CRUDController { - protected string $model = Department::class; - - protected string $view = 'Department'; - protected array $search = ['name']; protected array $rules = [ diff --git a/app/Http/Controllers/EditionCRUDController.php b/app/Http/Controllers/EditionCRUDController.php index eaa633e8..f566809d 100644 --- a/app/Http/Controllers/EditionCRUDController.php +++ b/app/Http/Controllers/EditionCRUDController.php @@ -2,14 +2,8 @@ namespace App\Http\Controllers; -use App\Models\Edition; - class EditionCRUDController extends CRUDController { - protected string $model = Edition::class; - - protected string $view = 'Edition'; - protected array $rules = [ 'name' => 'required|string', 'year' => 'required|integer', diff --git a/app/Http/Controllers/EventCRUDController.php b/app/Http/Controllers/EventCRUDController.php index e0a52166..c5788a7e 100644 --- a/app/Http/Controllers/EventCRUDController.php +++ b/app/Http/Controllers/EventCRUDController.php @@ -2,15 +2,10 @@ namespace App\Http\Controllers; -use App\Models\Event; use App\Models\EventDay; class EventCRUDController extends CRUDController { - protected string $model = Event::class; - - protected string $view = 'Event'; - protected array $rules = [ 'name' => 'required|string', 'time_start' => 'required|date_format:"H:i"', diff --git a/app/Http/Controllers/EventDayCRUDController.php b/app/Http/Controllers/EventDayCRUDController.php index a464ad0c..7071aa04 100644 --- a/app/Http/Controllers/EventDayCRUDController.php +++ b/app/Http/Controllers/EventDayCRUDController.php @@ -3,14 +3,9 @@ namespace App\Http\Controllers; use App\Models\Edition; -use App\Models\EventDay; class EventDayCRUDController extends CRUDController { - protected string $model = EventDay::class; - - protected string $view = 'EventDay'; - protected array $rules = [ 'edition_id' => 'required|integer|in:editions,id', 'date' => 'required|date', diff --git a/app/Http/Controllers/ProductCRUDController.php b/app/Http/Controllers/ProductCRUDController.php index 10aa74e4..2ba5a9f5 100644 --- a/app/Http/Controllers/ProductCRUDController.php +++ b/app/Http/Controllers/ProductCRUDController.php @@ -3,14 +3,9 @@ namespace App\Http\Controllers; use App\Models\Edition; -use App\Models\Product; class ProductCRUDController extends CRUDController { - protected string $model = Product::class; - - protected string $view = 'Product'; - protected array $rules = [ 'name' => 'required|string', 'price' => 'required|integer', diff --git a/app/Http/Controllers/QuestCRUDController.php b/app/Http/Controllers/QuestCRUDController.php index 3f59323f..7098b135 100644 --- a/app/Http/Controllers/QuestCRUDController.php +++ b/app/Http/Controllers/QuestCRUDController.php @@ -4,14 +4,9 @@ use App\Models\Company; use App\Models\Edition; -use App\Models\Quest; class QuestCRUDController extends CRUDController { - protected string $model = Quest::class; - - protected string $view = 'Quest'; - protected array $rules = [ 'name' => 'required|string', 'category' => 'required|string|in:company,talk,workshop,milestone,teambuiling', diff --git a/app/Http/Controllers/SpeakerCRUDController.php b/app/Http/Controllers/SpeakerCRUDController.php index 210a7490..371093a4 100644 --- a/app/Http/Controllers/SpeakerCRUDController.php +++ b/app/Http/Controllers/SpeakerCRUDController.php @@ -9,10 +9,6 @@ class SpeakerCRUDController extends CRUDController { - protected string $model = Speaker::class; - - protected string $view = 'Speaker'; - protected array $rules = [ 'name' => 'required|string', 'title' => 'nullable|string', diff --git a/app/Http/Controllers/SponsorCRUDController.php b/app/Http/Controllers/SponsorCRUDController.php index 8e04dea1..47d1bedc 100644 --- a/app/Http/Controllers/SponsorCRUDController.php +++ b/app/Http/Controllers/SponsorCRUDController.php @@ -4,14 +4,9 @@ use App\Models\Company; use App\Models\Edition; -use App\Models\Sponsor; class SponsorCRUDController extends CRUDController { - protected string $model = Sponsor::class; - - protected string $view = 'Sponsor'; - protected array $rules = [ 'tier' => 'required|in:platinum,gold,silver', 'edition_id' => 'required|exists:editions,id', diff --git a/app/Http/Controllers/StaffCRUDController.php b/app/Http/Controllers/StaffCRUDController.php index 07cc01a2..a6bbdf23 100644 --- a/app/Http/Controllers/StaffCRUDController.php +++ b/app/Http/Controllers/StaffCRUDController.php @@ -5,14 +5,9 @@ use App\Models\Department; use App\Models\Edition; use App\Models\Participant; -use App\Models\Staff; class StaffCRUDController extends CRUDController { - protected string $model = Staff::class; - - protected string $view = 'Staff'; - protected array $rules = [ 'participant_id' => 'required|exists:participants,id', 'department_id' => 'required|exists:departments,id', diff --git a/app/Http/Controllers/StandCRUDController.php b/app/Http/Controllers/StandCRUDController.php index 90de3dcc..8fea9cb9 100644 --- a/app/Http/Controllers/StandCRUDController.php +++ b/app/Http/Controllers/StandCRUDController.php @@ -4,14 +4,9 @@ use App\Models\EventDay; use App\Models\Sponsor; -use App\Models\Stand; class StandCRUDController extends CRUDController { - protected string $model = Stand::class; - - protected string $view = 'Stand'; - protected array $rules = [ 'sponsor_id' => 'required|integer|in:sponsors,id', 'event_day_id' => 'required|integer|in:event_days,id', diff --git a/app/Http/Controllers/UserCRUDController.php b/app/Http/Controllers/UserCRUDController.php index 62d2efd1..2faf3145 100644 --- a/app/Http/Controllers/UserCRUDController.php +++ b/app/Http/Controllers/UserCRUDController.php @@ -11,10 +11,6 @@ class UserCRUDController extends CRUDController { - protected string $model = User::class; - - protected string $view = 'User'; - protected array $rules = [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users,email', diff --git a/database/factories/AdminFactory.php b/database/factories/AdminFactory.php index fbad3704..f1d155f1 100644 --- a/database/factories/AdminFactory.php +++ b/database/factories/AdminFactory.php @@ -2,6 +2,8 @@ namespace Database\Factories; +use App\Models\Admin; +use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -9,6 +11,27 @@ */ class AdminFactory extends Factory { + public function configure(): static + { + return $this->afterMaking(function (Admin $admin) { + if ($admin->user_id !== 0) { + return; + } + + $admin->user()->associate(User::factory()->create([ + 'usertype_id' => -1, + 'usertype_type' => Admin::class, + ])); + })->afterCreating(function (Admin $admin) { + if ($admin->user_id !== 0) { + return; + } + + $admin->user->usertype_id = $admin->id; + $admin->user->save(); + }); + } + /** * Define the model's default state. * diff --git a/database/factories/CompanyFactory.php b/database/factories/CompanyFactory.php index 87b2e0c8..1ef8406a 100644 --- a/database/factories/CompanyFactory.php +++ b/database/factories/CompanyFactory.php @@ -2,7 +2,9 @@ namespace Database\Factories; +use App\Models\Company; use App\Models\SocialMedia; +use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -10,6 +12,27 @@ */ class CompanyFactory extends Factory { + public function configure(): static + { + return $this->afterMaking(function (Company $company) { + if ($company->user_id !== 0) { + return; + } + + $company->user()->associate(User::factory()->create([ + 'usertype_id' => -1, + 'usertype_type' => Company::class, + ])); + })->afterCreating(function (Company $company) { + if ($company->user_id !== 0) { + return; + } + + $company->user->usertype_id = $company->id; + $company->user->save(); + }); + } + /** * Define the model's default state. * diff --git a/database/factories/ParticipantFactory.php b/database/factories/ParticipantFactory.php index e754837f..f87204f1 100644 --- a/database/factories/ParticipantFactory.php +++ b/database/factories/ParticipantFactory.php @@ -2,7 +2,9 @@ namespace Database\Factories; +use App\Models\Participant; use App\Models\SocialMedia; +use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -10,6 +12,27 @@ */ class ParticipantFactory extends Factory { + public function configure(): static + { + return $this->afterMaking(function (Participant $participant) { + if ($participant->user_id !== 0) { + return; + } + + $participant->user()->associate(User::factory()->create([ + 'usertype_id' => -1, + 'usertype_type' => Participant::class, + ])); + })->afterCreating(function (Participant $participant) { + if ($participant->user_id !== 0) { + return; + } + + $participant->user->usertype_id = $participant->id; + $participant->user->save(); + }); + } + /** * Define the model's default state. * diff --git a/database/factories/QuestFactory.php b/database/factories/QuestFactory.php index 82c0ccac..149e1f7d 100644 --- a/database/factories/QuestFactory.php +++ b/database/factories/QuestFactory.php @@ -2,7 +2,9 @@ namespace Database\Factories; +use App\Models\Company; use App\Models\Edition; +use App\Models\Event; use Illuminate\Database\Eloquent\Factories\Factory; /** @@ -21,6 +23,8 @@ public function definition(): array 'name' => fake()->words(3, true), 'category' => fake()->randomElement(['COMPANY', 'TALK', 'WORKSHOP', 'MILESTONE', 'TEAMBUILDING']), 'edition_id' => Edition::factory(), + 'requirement_type' => fake()->randomElement([Company::class, Event::class]), + 'requirement_id' => fn (array $attributes) => $attributes['requirement_type']::factory(), ]; } } diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index 4ef188e4..de0cf370 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -24,6 +24,10 @@ class UserFactory extends Factory public function configure(): static { return $this->afterCreating(function (User $user) { + if ($user->usertype_id !== 0) { + return; + } + $user->usertype()->associate($user->usertype_type::factory()->create([ 'user_id' => $user->id, ])); diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index ef5ea480..873cbc1a 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -57,14 +57,11 @@ public function run(): void { self::cleanDatabase(); - $participants = User::factory(100)->create(); - $companies = User::factory(10)->company()->create(); - - if (! User::where('email', '=', DatabaseSeeder::DEFAULT_ADMIN_EMAIL)->exists()) { - User::factory()->admin()->create([ - 'email' => DatabaseSeeder::DEFAULT_ADMIN_EMAIL, - ]); - } + User::factory()->admin()->create([ + 'email' => DatabaseSeeder::DEFAULT_ADMIN_EMAIL, + ]); + $participants = Participant::factory(100)->create(); + $companies = Company::factory(10)->create(); $edition = Edition::factory()->create(); @@ -75,7 +72,7 @@ public function run(): void ]), range(0, 7)); $departments = Department::factory(10)->recycle($edition)->create(); - Staff::factory(20)->recycle($departments)->recycle($participants->pluck('usertype'))->create(); + Staff::factory(20)->recycle($departments)->recycle($participants)->create(); foreach ($event_days as $day) { $events = Event::factory(10)->recycle($day)->create(); @@ -84,8 +81,8 @@ public function run(): void $sponsors = []; foreach ($companies as $company) { - Quest::factory()->recycle($edition)->for($company->usertype, 'requirement')->create(); - $sponsors[] = Sponsor::factory()->recycle($edition)->recycle($company->usertype)->create(); + Quest::factory()->recycle($edition)->for($company, 'requirement')->create(); + $sponsors[] = Sponsor::factory()->recycle($edition)->recycle($company)->create(); } Stand::factory(20)->recycle($event_days)->recycle($sponsors)->create(); diff --git a/tests/CRUDTestCase.php b/tests/CRUDTestCase.php new file mode 100644 index 00000000..5e3c26bc --- /dev/null +++ b/tests/CRUDTestCase.php @@ -0,0 +1,272 @@ +user = User::factory()->create(); + $this->admin = User::factory()->admin()->create(); + + $basename = Str::replaceEnd('CRUDTest', '', class_basename($this)); + + $this->model ??= "App\\Models\\{$basename}"; + $this->controller ??= "App\\Http\\Controllers\\{$basename}CRUDController"; + } + + public function test_index_screen_cannot_be_rendered(): void + { + $response = $this->get(action([$this->controller, 'index'])); + + $response->assertRedirect(AUTH_REDIRECT); + } + + public function test_index_screen_cannot_be_rendered_if_not_admin(): void + { + $this->actingAs($this->user); + + $response = $this->get(action([$this->controller, 'index'])); + + $response->assertForbidden(); + } + + public function test_index_screen_can_be_rendered_if_admin(): void + { + $this->actingAs($this->admin); + + $response = $this->get(action([$this->controller, 'index'])); + + $response->assertOk(); + } + + public function test_show_screen_cannot_be_rendered(): void + { + $item = $this->model::factory()->create(); + + $response = $this->get(action([$this->controller, 'show'], $item)); + + $response->assertRedirect(AUTH_REDIRECT); + } + + public function test_show_screen_cannot_be_rendered_if_not_admin(): void + { + $item = $this->model::factory()->create(); + + $this->actingAs($this->user); + + $response = $this->get(action([$this->controller, 'show'], $item)); + + $response->assertForbidden(); + } + + public function test_show_screen_can_be_rendered_if_admin(): void + { + $item = $this->model::factory()->create(); + + $this->actingAs($this->admin); + + $response = $this->get(action([$this->controller, 'show'], $item)); + + $response->assertOk(); + } + + public function test_create_screen_cannot_be_rendered(): void + { + $response = $this->get(action([$this->controller, 'create'])); + + $response->assertRedirect(AUTH_REDIRECT); + } + + public function test_create_screen_cannot_be_rendered_if_not_admin(): void + { + $this->actingAs($this->user); + + $response = $this->get(action([$this->controller, 'create'])); + + $response->assertForbidden(); + } + + public function test_create_screen_can_be_rendered_if_admin(): void + { + $this->actingAs($this->admin); + + $response = $this->get(action([$this->controller, 'create'])); + + $response->assertOk(); + } + + public function test_edit_screen_cannot_be_rendered(): void + { + $item = $this->model::factory()->create(); + + $response = $this->get(action([$this->controller, 'edit'], $item)); + + $response->assertRedirect(AUTH_REDIRECT); + } + + public function test_edit_screen_cannot_be_rendered_if_not_admin(): void + { + $item = $this->model::factory()->create(); + + $this->actingAs($this->user); + + $response = $this->get(action([$this->controller, 'edit'], $item)); + + $response->assertForbidden(); + } + + public function test_edit_screen_can_be_rendered_if_admin(): void + { + $item = $this->model::factory()->create(); + + $this->actingAs($this->admin); + + $response = $this->get(action([$this->controller, 'edit'], $item)); + + $response->assertOk(); + } + + public function test_store_action_cannot_be_executed(): void + { + $response = $this->post(action([$this->controller, 'store'])); + + $response->assertRedirect(AUTH_REDIRECT); + } + + public function test_store_action_cannot_be_executed_if_not_admin(): void + { + $this->actingAs($this->user); + + $response = $this->post(action([$this->controller, 'store'])); + + $response->assertForbidden(); + } + + public function test_store_action_can_be_executed_if_admin(): void + { + $this->actingAs($this->admin); + + $response = $this->post(action([$this->controller, 'store'])); + + $response->assertInvalid(); + } + + public function test_store_action_can_be_executed_if_admin_with_valid_data(): void + { + $this->actingAs($this->admin); + + $modelCount = $this->model::count(); + + foreach ($this->validCreateData as $data) { + $response = $this->post(action([$this->controller, 'store']), $data); + + $response->assertValid(); + + $this->assertEquals(++$modelCount, $this->model::count()); + } + } + + public function test_update_action_cannot_be_executed(): void + { + $item = $this->model::factory()->create(); + + $response = $this->put(action([$this->controller, 'update'], $item)); + + $response->assertRedirect(AUTH_REDIRECT); + } + + public function test_update_action_cannot_be_executed_if_not_admin(): void + { + $item = $this->model::factory()->create(); + + $this->actingAs($this->user); + + $response = $this->put(action([$this->controller, 'update'], $item)); + + $response->assertForbidden(); + } + + public function test_update_action_can_be_executed_if_admin(): void + { + $item = $this->model::factory()->create(); + + $this->actingAs($this->admin); + + $response = $this->put(action([$this->controller, 'update'], $item)); + + $response->assertInvalid(); + } + + public function test_update_action_can_be_executed_if_admin_with_valid_data(): void + { + $this->actingAs($this->admin); + + foreach ($this->validUpdateData as $data) { + $item = $this->model::factory()->create(); + + $response = $this->put(action([$this->controller, 'update'], $item), $data); + + $response->assertValid(); + + $this->assertNotEquals($item->toArray(), $this->model::find($item->id)->toArray()); + } + } + + public function test_destroy_action_cannot_be_executed(): void + { + $item = $this->model::factory()->create(); + + $response = $this->delete(action([$this->controller, 'destroy'], $item)); + + $response->assertRedirect(AUTH_REDIRECT); + } + + public function test_destroy_action_cannot_be_executed_if_not_admin(): void + { + $item = $this->model::factory()->create(); + + $this->actingAs($this->user); + + $response = $this->delete(action([$this->controller, 'destroy'], $item)); + + $response->assertForbidden(); + } + + public function test_destroy_action_can_be_executed_if_admin(): void + { + $item = $this->model::factory()->create(); + + $this->actingAs($this->admin); + + $response = $this->delete(action([$this->controller, 'destroy'], $item)); + + $response->assertValid(); + + $this->assertNull($this->model::find($item->id)); + } +} diff --git a/tests/Feature/CompetitionCRUDTest.php.php b/tests/Feature/CompetitionCRUDTest.php.php new file mode 100644 index 00000000..75839872 --- /dev/null +++ b/tests/Feature/CompetitionCRUDTest.php.php @@ -0,0 +1,9 @@ + 'Test admin', + 'email' => 'admin@example.com', + 'type' => 'admin', + ], + // Participant + [ + 'name' => 'Test participant', + 'email' => 'participant@example.com', + 'type' => 'participant', + ], + [ + 'name' => 'Test participant with social media', + 'email' => 'socialmedia@example.com', + 'type' => 'participant', + 'social_media' => [ + 'email' => 'test@example.com', + 'facebook' => 'test', + 'github' => 'test', + 'instagram' => 'test', + 'linkedin' => 'test', + 'twitter' => 'test', + 'website' => 'https://example.com', + ], + ], + // Company + [ + 'name' => 'Test company', + 'email' => 'company@example.com', + 'type' => 'company', + ], + [ + 'name' => 'Test company with social media and description', + 'email' => 'companysocialmedia@example.com', + 'type' => 'company', + 'description' => 'Test description', + 'social_media' => [ + 'email' => 'test@example.com', + 'facebook' => 'test', + 'github' => 'test', + 'instagram' => 'test', + 'linkedin' => 'test', + 'twitter' => 'test', + 'website' => 'https://example.com', + ], + ], + ]; + + protected array $validUpdateData = [ + // Admin + [ + 'name' => 'Test participant updated', + 'email' => 'test@exampl.com', + 'type' => 'participant', + ], + ]; +}