Write tests - Write tests for what was done to this point that will be kept #11

Merged
okorpheus merged 61 commits from write-tests into master 2024-07-05 21:21:32 +00:00
14 changed files with 413 additions and 16 deletions
Showing only changes of commit cd26479fd9 - Show all commits

View File

@ -49,6 +49,7 @@ class SchoolController extends Controller
if (! Auth::user()->is_admin) { if (! Auth::user()->is_admin) {
abort(403); abort(403);
} }
$school->loadCount('students');
return view('admin.schools.edit', ['school' => $school]); return view('admin.schools.edit', ['school' => $school]);
} }
@ -71,7 +72,8 @@ class SchoolController extends Controller
'zip' => request('zip'), 'zip' => request('zip'),
]); ]);
return redirect()->route('admin.schools.show', ['school' => $school->id])->with('success', 'School '.$school->name.' updated'); return redirect()->route('admin.schools.show', ['school' => $school->id])->with('success',
'School '.$school->name.' updated');
} }
public function create() public function create()
@ -104,6 +106,17 @@ class SchoolController extends Controller
return redirect('/admin/schools')->with('success', 'School '.$school->name.' created'); return redirect('/admin/schools')->with('success', 'School '.$school->name.' created');
} }
public function destroy(School $school)
{
if ($school->students()->count() > 0) {
return to_route('admin.schools.index')->with('error', 'You cannot delete a school with students.');
}
$name = $school->name;
$school->delete();
return to_route('admin.schools.index')->with('success', 'School '.$school->name.' deleted');
}
public function add_domain(School $school) public function add_domain(School $school)
{ {
if (! Auth::user()->is_admin) { if (! Auth::user()->is_admin) {
@ -115,7 +128,8 @@ class SchoolController extends Controller
]); ]);
SchoolEmailDomain::updateOrInsert([ SchoolEmailDomain::updateOrInsert([
'school_id' => $school->id, 'school_id' => $school->id,
'domain' => request('domain')]); 'domain' => request('domain'),
]);
return redirect()->route('admin.schools.show', $school)->with('success', 'Domain Added'); return redirect()->route('admin.schools.show', $school)->with('success', 'Domain Added');

View File

@ -9,7 +9,9 @@ use App\Models\Student;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use function abort; use function abort;
use function to_route;
use function view; use function view;
class StudentController extends Controller class StudentController extends Controller
@ -19,7 +21,7 @@ class StudentController extends Controller
if (! Auth::user()->is_admin) { if (! Auth::user()->is_admin) {
abort(403); abort(403);
} }
$students = Student::with(['school', 'entries'])->orderBy('last_name')->orderBy('first_name')->paginate(15); $students = Student::with(['school'])->withCount('entries')->orderBy('last_name')->orderBy('first_name')->paginate(15);
return view('admin.students.index', ['students' => $students]); return view('admin.students.index', ['students' => $students]);
} }
@ -66,8 +68,9 @@ class StudentController extends Controller
$minGrade = Audition::min('minimum_grade'); $minGrade = Audition::min('minimum_grade');
$maxGrade = Audition::max('maximum_grade'); $maxGrade = Audition::max('maximum_grade');
$schools = School::orderBy('name')->get(); $schools = School::orderBy('name')->get();
$student->loadCount('entries');
return view('admin.students.edit', ['student' => $student, 'schools' => $schools, 'minGrade' => $minGrade, 'maxGrade' => $maxGrade]); return view('admin.students.edit',
['student' => $student, 'schools' => $schools, 'minGrade' => $minGrade, 'maxGrade' => $maxGrade]);
} }
public function update(Request $request, Student $student) public function update(Request $request, Student $student)
@ -84,7 +87,8 @@ class StudentController extends Controller
foreach ($student->entries as $entry) { foreach ($student->entries as $entry) {
if ($entry->audition->minimum_grade > request('grade') || $entry->audition->maximum_grade < request('grade')) { if ($entry->audition->minimum_grade > request('grade') || $entry->audition->maximum_grade < request('grade')) {
return redirect('/admin/students/'.$student->id.'/edit')->with('error', 'This student is entered in an audition that is not available to their new grade.'); return redirect('/admin/students/'.$student->id.'/edit')->with('error',
'This student is entered in an audition that is not available to their new grade.');
} }
} }
@ -98,4 +102,16 @@ class StudentController extends Controller
return redirect('/admin/students'); return redirect('/admin/students');
} }
public function destroy(Student $student)
{
Log::debug('Deleting student '.$student->id);
if($student->entries()->count() > 0) {
return to_route('admin.students.index')->with('error', 'You cannot delete a student with entries.');
}
$name = $student->full_name();
$student->delete();
return to_route('admin.students.index')->with('success', 'Student '.$name.' deleted successfully.');
}
} }

View File

@ -9,6 +9,7 @@
"codedge/laravel-fpdf": "^1.12", "codedge/laravel-fpdf": "^1.12",
"laravel/fortify": "^1.21", "laravel/fortify": "^1.21",
"laravel/framework": "^11.0", "laravel/framework": "^11.0",
"laravel/pail": "^1.1",
"laravel/tinker": "^2.9", "laravel/tinker": "^2.9",
"predis/predis": "^2.2", "predis/predis": "^2.2",
"symfony/http-client": "^7.1", "symfony/http-client": "^7.1",

80
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "91ced10faccf6fc9b3fe05fe288ec1d6", "content-hash": "a21ed75b45b3f61cbc76446701fbc3ce",
"packages": [ "packages": [
{ {
"name": "bacon/bacon-qr-code", "name": "bacon/bacon-qr-code",
@ -1491,6 +1491,84 @@
}, },
"time": "2024-05-21T17:57:45+00:00" "time": "2024-05-21T17:57:45+00:00"
}, },
{
"name": "laravel/pail",
"version": "v1.1.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/pail.git",
"reference": "c22fe771277971eb9cd224955996bcf39c1a710d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/pail/zipball/c22fe771277971eb9cd224955996bcf39c1a710d",
"reference": "c22fe771277971eb9cd224955996bcf39c1a710d",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"ext-pcntl": "*",
"illuminate/console": "^10.24|^11.0",
"illuminate/contracts": "^10.24|^11.0",
"illuminate/log": "^10.24|^11.0",
"illuminate/process": "^10.24|^11.0",
"illuminate/support": "^10.24|^11.0",
"nunomaduro/termwind": "^1.15|^2.0",
"php": "^8.2",
"symfony/console": "^6.0|^7.0"
},
"require-dev": {
"laravel/pint": "^1.13",
"orchestra/testbench": "^8.12|^9.0",
"pestphp/pest": "^2.20",
"pestphp/pest-plugin-type-coverage": "^2.3",
"phpstan/phpstan": "^1.10",
"symfony/var-dumper": "^6.3|^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
},
"laravel": {
"providers": [
"Laravel\\Pail\\PailServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Pail\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
},
{
"name": "Nuno Maduro",
"email": "enunomaduro@gmail.com"
}
],
"description": "Easily delve into your Laravel application's log files directly from the command line.",
"homepage": "https://github.com/laravel/pail",
"keywords": [
"laravel",
"logs",
"php",
"tail"
],
"support": {
"issues": "https://github.com/laravel/pail/issues",
"source": "https://github.com/laravel/pail"
},
"time": "2024-05-08T18:19:39+00:00"
},
{ {
"name": "laravel/prompts", "name": "laravel/prompts",
"version": "v0.1.22", "version": "v0.1.22",

View File

@ -1,7 +1,16 @@
<x-layout.app> <x-layout.app>
<x-card.card class="mx-auto max-w-xl"> <x-card.card class="mx-auto max-w-xl">
<x-card.heading>Edit School</x-card.heading> <x-card.heading>
Edit School
@if($school->students_count === 0)
<x-slot:right_side>
<x-delete-resource-modal action="{{ route('admin.schools.destroy',$school) }}" title="Delete school {{ $school->name }}">
Confirm you would like to delete the school {{ $school->name }}. This action cannot be undone.
</x-delete-resource-modal>
</x-slot:right_side>
@endif
</x-card.heading>
<x-form.form method="PATCH" action="{{ route('admin.schools.update',$school) }}"> <x-form.form method="PATCH" action="{{ route('admin.schools.update',$school) }}">
<x-form.body-grid> <x-form.body-grid>
<x-form.field name="name" label_text="Name" colspan="6" value="{{ $school->name }}" /> <x-form.field name="name" label_text="Name" colspan="6" value="{{ $school->name }}" />

View File

@ -1,10 +1,19 @@
<x-layout.app> <x-layout.app>
<x-card.card class="mx-auto max-w-xl"> <x-card.card class="mx-auto max-w-xl">
<x-card.heading>Edit Student</x-card.heading> <x-card.heading>
Edit Student
@if($student->entries_count === 0)
<x-slot:right_side>
<x-delete-resource-modal title="Delete Student {{ $student->full_name() }} from {{ $student->school->name }}" :action="route('admin.students.destroy',$student)">
Please confirm you'd like to delete this student. This action cannot be undone.
</x-delete-resource-modal>
</x-slot:right_side>
@endif
</x-card.heading>
<x-form.form method="PATCH" action="{{ route('admin.students.update',$student) }}"> <x-form.form method="PATCH" action="{{ route('admin.students.update',$student) }}">
<x-form.body-grid columns="12"> <x-form.body-grid columns="8">
<x-form.field name="first_name" label_text="First Name" colspan="5" value="{{ $student->first_name }}" /> <x-form.field name="first_name" label_text="First Name" colspan="3" value="{{ $student->first_name }}" />
<x-form.field name="last_name" label_text="Last Name" colspan="5" value="{{ $student->last_name }}" /> <x-form.field name="last_name" label_text="Last Name" colspan="3" value="{{ $student->last_name }}" />
<x-form.select name="grade" colspan="2"> <x-form.select name="grade" colspan="2">
<x-slot:label>Grade</x-slot:label> <x-slot:label>Grade</x-slot:label>
@php($n = $minGrade) @php($n = $minGrade)
@ -13,7 +22,7 @@
@php($n++); @php($n++);
@endwhile @endwhile
</x-form.select> </x-form.select>
<x-form.select name="school_id" colspan="12"> <x-form.select name="school_id" colspan="8">
<x-slot:label>School</x-slot:label> <x-slot:label>School</x-slot:label>
@foreach ($schools as $school) @foreach ($schools as $school)

View File

@ -23,7 +23,7 @@
<x-table.td><a href="{{ route('admin.students.edit',$student) }}">{{ $student->full_name(true) }}</a></x-table.td> <x-table.td><a href="{{ route('admin.students.edit',$student) }}">{{ $student->full_name(true) }}</a></x-table.td>
<x-table.td>{{ $student->school->name }}</x-table.td> <x-table.td>{{ $student->school->name }}</x-table.td>
<x-table.td>{{ $student->grade }}</x-table.td> <x-table.td>{{ $student->grade }}</x-table.td>
<x-table.td>{{ $student->entries->count() }}</x-table.td> <x-table.td>{{ $student->entries_count }}</x-table.td>
</tr> </tr>
@endforeach @endforeach
</x-table.body> </x-table.body>

View File

@ -0,0 +1,126 @@
@props(['size' => 20,'title','method'=>'DELETE','action'])
<div
x-data="{ 'showModal': false }"
@keydown.escape="showModal = false"
>
<!-- Trigger for Modal -->
<button type="button" @click="showModal = true" class="rounded-lg bg-red-600 p-1.5 text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600">
{{-- trash button to show modal --}}
<svg xmlns="http://www.w3.org/2000/svg" width="{{ $size }}" height="{{ $size }}" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>
</svg>
</button>
<!-- Modal -->
<div class="relative z-10"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
x-show="showModal"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
x-cloak>
<!--
Background backdrop, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0"
To: "opacity-100"
Leaving: "ease-in duration-200"
From: "opacity-100"
To: "opacity-0"
-->
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<div class="fixed inset-0 z-10 w-screen overflow-y-auto">
<div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-cloak
x-show="showModal">
<!--
Modal panel, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
To: "opacity-100 translate-y-0 sm:scale-100"
Leaving: "ease-in duration-200"
From: "opacity-100 translate-y-0 sm:scale-100"
To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
-->
<div
class="relative transform overflow-hidden rounded-lg bg-white px-4 pb-4 pt-5 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-lg sm:p-6"
x-transition:enter="ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave="ease-in duration-200"
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
x-cloak
x-show="showModal"
@click.away="showModal = false">
<div class="absolute right-0 top-0 hidden pr-4 pt-4 sm:block">
<button type="button"
@click="showModal = false"
class="rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2">
<span class="sr-only">Close</span>
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
</svg>
</button>
</div>
<div class="sm:flex sm:items-start">
<div
class="mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-red-100 sm:mx-0 sm:h-10 sm:w-10">
<svg class="h-6 w-6 text-red-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round"
d="M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"/>
</svg>
</div>
<div class="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<h3 class="text-base font-semibold leading-6 text-gray-900" id="modal-title">
{{ $title }}
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">
{{ $slot }}
</p>
</div>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<form method="{{ $method == 'GET' ? 'GET':'POST' }}" action="{{$action}}">
@csrf
@if(! in_array($method, ['POST','GET']))
@method($method)
@endif
<button type="submit"
class="inline-flex w-full justify-center rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 sm:ml-3 sm:w-auto">
Delete
</button>
</form>
<button type="button"
@click="showModal = false"
class="mt-3 inline-flex w-full justify-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50 sm:mt-0 sm:w-auto">
Cancel
</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -10,5 +10,4 @@
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/> <path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5m3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0z"/>
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/> <path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1zM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4zM2.5 3h11V2h-11z"/>
</svg> </svg>
</svg>
</button> </button>

View File

@ -90,6 +90,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
Route::post('/', 'store')->name('admin.students.store'); Route::post('/', 'store')->name('admin.students.store');
Route::get('/{student}/edit', 'edit')->name('admin.students.edit'); Route::get('/{student}/edit', 'edit')->name('admin.students.edit');
Route::patch('/{student}', 'update')->name('admin.students.update'); Route::patch('/{student}', 'update')->name('admin.students.update');
Route::delete('/{student}', 'destroy')->name('admin.students.destroy');
}); });
// Admin School Routes // Admin School Routes
@ -103,6 +104,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->
Route::patch('/{school}', 'update')->name('admin.schools.update'); Route::patch('/{school}', 'update')->name('admin.schools.update');
Route::post('/', 'store')->name('admin.schools.store'); Route::post('/', 'store')->name('admin.schools.store');
Route::delete('/domain/{domain}', 'destroy_domain')->name('admin.schools.destroy_domain'); Route::delete('/domain/{domain}', 'destroy_domain')->name('admin.schools.destroy_domain');
Route::delete('/{school}', 'destroy')->name('admin.schools.destroy');
}); });

View File

@ -6,3 +6,9 @@ use Illuminate\Support\Facades\Artisan;
Artisan::command('inspire', function () { Artisan::command('inspire', function () {
$this->comment(Inspiring::quote()); $this->comment(Inspiring::quote());
})->purpose('Display an inspiring quote')->hourly(); })->purpose('Display an inspiring quote')->hourly();
Artisan::command('logs:remove', function () {
exec('rm -f '.storage_path('logs/*.log'));
exec('rm -f '.base_path('*.log'));
$this->comment('Logs have been removed!');
})->describe('Remove log files');

2
storage/pail/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -1,7 +1,9 @@
<?php <?php
use App\Models\Entry;
use App\Models\School; use App\Models\School;
use App\Models\SchoolEmailDomain; use App\Models\SchoolEmailDomain;
use App\Models\Student;
use App\Models\User; use App\Models\User;
use Database\Factories\SchoolEmailDomainFactory; use Database\Factories\SchoolEmailDomainFactory;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
@ -235,3 +237,69 @@ it('allows an administrator to update a school', function () {
->assertOk() ->assertOk()
->assertSee($newData['name']); ->assertSee($newData['name']);
}); });
it('includes a form to destroy the school IF it has no students', function () {
// Arrange
$condemnedSchool = School::factory()->create();
actingAs($this->adminUser);
// Act & Assert
get(route('admin.schools.edit', $condemnedSchool))
->assertOk()
->assertSeeInOrder([
'form',
'method',
'POST',
'action=',
route('admin.schools.destroy', $condemnedSchool),
'/form',
], false)
->assertSee('<input type="hidden" name="_method" value="DELETE">', false);
});
it('does not include the destruction form if the school has students', function () {
// Arrange
$condemnedSchool = School::factory()->create();
Student::factory()->create(['school_id' => $condemnedSchool->id]);
actingAs($this->adminUser);
// Act & Assert
get(route('admin.schools.edit', $condemnedSchool))
->assertOk()
->assertDontSee('<input type="hidden" name="_method" value="DELETE">', false);
});
it('allows an administrator to destroy a school without students', function () {
// Arrange
$condemnedSchool = School::factory()->create();
// Act & Assert
expect($condemnedSchool->exists())->toBeTrue();
actingAs($this->adminUser);
/** @noinspection PhpUnhandledExceptionInspection */
delete(route('admin.schools.destroy', $condemnedSchool))
->assertSessionHasNoErrors()
->assertRedirect(route('admin.schools.index'));
expect(School::find($condemnedSchool->id))->toBeNull();
});
it('does not allow an administrator to destroy a student with entries', function () {
// Arrange
$condemnedSchool = School::factory()->create();
Student::factory()->create(['school_id' => $condemnedSchool->id]);
// Act & Assert
expect($condemnedSchool->exists())->toBeTrue();
actingAs($this->adminUser);
/** @noinspection PhpUnhandledExceptionInspection */
delete(route('admin.schools.destroy', $condemnedSchool))
->assertSessionHas('error', 'You cannot delete a school with students.')
->assertRedirect(route('admin.schools.index'))
->assertSessionHasNoErrors();
expect(School::find($condemnedSchool->id))->toBeInstanceOf(School::class);
});
it('does not allow a non administrator to delete a student', function () {
// Arrange
$condemnedSchool = School::factory()->create();
// Act & Assert
expect($condemnedSchool->exists())->toBeTrue();
actingAs(User::factory()->create());
/** @noinspection PhpUnhandledExceptionInspection */
delete(route('admin.schools.destroy', $condemnedSchool))
->assertSessionHasNoErrors()
->assertSessionHas('error', 'You do not have admin access.')
->assertRedirect(route('dashboard'));
expect(School::find($condemnedSchool->id))->toBeInstanceOf(School::class);
});

View File

@ -1,12 +1,14 @@
<?php <?php
use App\Models\Audition; use App\Models\Audition;
use App\Models\Entry;
use App\Models\School; use App\Models\School;
use App\Models\Student; use App\Models\Student;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use function Pest\Laravel\actingAs; use function Pest\Laravel\actingAs;
use function Pest\Laravel\delete;
use function Pest\Laravel\get; use function Pest\Laravel\get;
use function Pest\Laravel\patch; use function Pest\Laravel\patch;
@ -107,7 +109,6 @@ it('is populated with existing data', function () {
// Arrange // Arrange
Audition::factory()->create(['minimum_grade' => 1, 'maximum_grade' => 18]); // Needed for the grade select Audition::factory()->create(['minimum_grade' => 1, 'maximum_grade' => 18]); // Needed for the grade select
actingAs($this->adminUser); actingAs($this->adminUser);
$fieldList = ['first_name', 'last_name', 'grade', 'school_id'];
// Act & Assert // Act & Assert
$response = get(route('admin.students.edit', $this->student)); $response = get(route('admin.students.edit', $this->student));
$response->assertOk(); $response->assertOk();
@ -157,3 +158,69 @@ it('allows an administrator to edit a student', function () {
->assertSee($newData['grade']) ->assertSee($newData['grade'])
->assertSee($newSchool->name); ->assertSee($newSchool->name);
}); });
it('includes a form to destroy the student IF they have no entries', function () {
// Arrange
$condemnedStudent = Student::factory()->create();
actingAs($this->adminUser);
// Act & Assert
get(route('admin.students.edit', $condemnedStudent))
->assertOk()
->assertSeeInOrder([
'form',
'method',
'POST',
'action=',
route('admin.students.destroy', $condemnedStudent),
'/form',
], false)
->assertSee('<input type="hidden" name="_method" value="DELETE">', false);
});
it('does not include the destruction form if the student has entries', function () {
// Arrange
$condemnedStudent = Student::factory()->create();
Entry::factory()->create(['student_id' => $condemnedStudent->id]);
actingAs($this->adminUser);
// Act & Assert
get(route('admin.students.edit', $condemnedStudent))
->assertOk()
->assertDontSee('<input type="hidden" name="_method" value="DELETE">', false);
});
it('allows an administrator to destroy a student without entries', function () {
// Arrange
$condemnedStudent = Student::factory()->create();
// Act & Assert
expect($condemnedStudent->exists())->toBeTrue();
actingAs($this->adminUser);
/** @noinspection PhpUnhandledExceptionInspection */
delete(route('admin.students.destroy', $condemnedStudent))
->assertSessionHasNoErrors()
->assertRedirect(route('admin.students.index'));
expect(Student::find($condemnedStudent->id))->toBeNull();
});
it('does not allow an administrator to destroy a student with entries', function () {
// Arrange
$condemnedStudent = Student::factory()->create();
Entry::factory()->create(['student_id' => $condemnedStudent->id]);
// Act & Assert
expect($condemnedStudent->exists())->toBeTrue();
actingAs($this->adminUser);
/** @noinspection PhpUnhandledExceptionInspection */
delete(route('admin.students.destroy', $condemnedStudent))
->assertSessionHas('error', 'You cannot delete a student with entries.')
->assertRedirect(route('admin.students.index'))
->assertSessionHasNoErrors();
expect(Student::find($condemnedStudent->id))->toBeInstanceOf(Student::class);
});
it('does not allow a non administrator to delete a student', function () {
// Arrange
$condemnedStudent = Student::factory()->create();
// Act & Assert
expect($condemnedStudent->exists())->toBeTrue();
actingAs(User::factory()->create());
/** @noinspection PhpUnhandledExceptionInspection */
delete(route('admin.students.destroy', $condemnedStudent))
->assertSessionHasNoErrors()
->assertSessionHas('error', 'You do not have admin access.')
->assertRedirect(route('dashboard'));
expect(Student::find($condemnedStudent->id))->toBeInstanceOf(Student::class);
});