Add tests for admin/SchoolController

This commit is contained in:
Matt Young 2025-07-07 23:56:52 -05:00
parent 2e8d625ab0
commit e4a646a4ce
3 changed files with 358 additions and 69 deletions

View File

@ -5,14 +5,12 @@ namespace App\Http\Controllers\Admin;
use App\Actions\Schools\CreateSchool; use App\Actions\Schools\CreateSchool;
use App\Actions\Schools\SetHeadDirector; use App\Actions\Schools\SetHeadDirector;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\AuditLogEntry; use App\Http\Requests\SchoolStoreRequest;
use App\Models\School; use App\Models\School;
use App\Models\SchoolEmailDomain; use App\Models\SchoolEmailDomain;
use App\Models\User; use App\Models\User;
use App\Services\Invoice\InvoiceDataService; use App\Services\Invoice\InvoiceDataService;
use Illuminate\Support\Facades\Auth;
use function abort;
use function redirect; use function redirect;
use function request; use function request;
@ -39,46 +37,25 @@ class SchoolController extends Controller
public function show(School $school) public function show(School $school)
{ {
if (! Auth::user()->is_admin) {
abort(403);
}
return view('admin.schools.show', ['school' => $school]); return view('admin.schools.show', ['school' => $school]);
} }
public function edit(School $school) public function edit(School $school)
{ {
if (! Auth::user()->is_admin) {
abort(403);
}
$school->loadCount('students'); $school->loadCount('students');
return view('admin.schools.edit', ['school' => $school]); return view('admin.schools.edit', ['school' => $school]);
} }
public function update(School $school) public function update(SchoolStoreRequest $request, School $school)
{ {
request()->validate([
'name' => ['required'],
'address' => ['required'],
'city' => ['required'],
'state' => ['required'],
'zip' => ['required'],
]);
$school->update([ $school->update([
'name' => request('name'), 'name' => $request['name'],
'address' => request('address'), 'address' => $request['address'],
'city' => request('city'), 'city' => $request['city'],
'state' => request('state'), 'state' => $request['state'],
'zip' => request('zip'), 'zip' => $request['zip'],
]);
$message = 'Modified school #'.$school->id.' - '.$school->name.' with address <br>'.$school->address.'<br>'.$school->city.', '.$school->state.' '.$school->zip;
AuditLogEntry::create([
'user' => auth()->user()->email,
'ip_address' => request()->ip(),
'message' => $message,
'affected' => ['schools' => [$school->id]],
]); ]);
return redirect()->route('admin.schools.show', ['school' => $school->id])->with('success', return redirect()->route('admin.schools.show', ['school' => $school->id])->with('success',
@ -87,48 +64,30 @@ class SchoolController extends Controller
public function create() public function create()
{ {
if (! Auth::user()->is_admin) {
abort(403);
}
return view('admin.schools.create'); return view('admin.schools.create');
} }
public function store() public function store(SchoolStoreRequest $request)
{ {
$creator = app(CreateSchool::class); $creator = app(CreateSchool::class);
$validData = request()->validate([
'name' => ['required', 'unique:schools,name'],
'address' => ['required'],
'city' => ['required'],
'state' => ['required'],
'zip' => ['required'],
]);
$school = $creator( $school = $creator(
$validData['name'], $request['name'],
$validData['address'], $request['address'],
$validData['city'], $request['city'],
$validData['state'], $request['state'],
$validData['zip'], $request['zip'],
); );
return redirect('/admin/schools')->with('success', 'School '.$school->name.' created'); return redirect(route('admin.schools.index'))->with('success', 'School '.$school->name.' created');
} }
public function destroy(School $school) public function destroy(School $school)
{ {
if ($school->students()->count() > 0) { if ($school->students()->count() > 0) {
return to_route('admin.schools.index')->with('error', 'You cannot delete a school with students.'); return to_route('admin.schools.index')->with('error', 'You cannot delete a school that has students.');
} }
$name = $school->name;
$message = 'Delete school #'.$school->id.' - '.$school->name;
AuditLogEntry::create([
'user' => auth()->user()->email,
'ip_address' => request()->ip(),
'message' => $message,
'affected' => ['schools' => [$school->id]],
]);
$school->delete(); $school->delete();
return to_route('admin.schools.index')->with('success', 'School '.$school->name.' deleted'); return to_route('admin.schools.index')->with('success', 'School '.$school->name.' deleted');
@ -136,9 +95,6 @@ class SchoolController extends Controller
public function add_domain(School $school) public function add_domain(School $school)
{ {
if (! Auth::user()->is_admin) {
abort(403);
}
request()->validate([ request()->validate([
// validate that the combination of school and domain is unique on the school_email_domains table // validate that the combination of school and domain is unique on the school_email_domains table
'domain' => ['required'], 'domain' => ['required'],
@ -147,12 +103,6 @@ class SchoolController extends Controller
'school_id' => $school->id, 'school_id' => $school->id,
'domain' => request('domain'), 'domain' => request('domain'),
]); ]);
AuditLogEntry::create([
'user' => auth()->user()->email,
'ip_address' => request()->ip(),
'message' => 'Added '.request('domain').' as an email domain for school #'.$school->id.' - '.$school->name,
'affected' => ['schools' => [$school->id]],
]);
return redirect()->route('admin.schools.show', $school)->with('success', 'Domain Added'); return redirect()->route('admin.schools.show', $school)->with('success', 'Domain Added');
@ -164,9 +114,11 @@ class SchoolController extends Controller
$domain->delete(); $domain->delete();
// return a redirect to the previous URL // return a redirect to the previous URL
return redirect()->back(); return redirect()->back()->with('success', 'Domain removed successfully.');
} }
// TODO: Add testing for invoicing
/** @codeCoverageIgnore */
public function viewInvoice(School $school) public function viewInvoice(School $school)
{ {
$invoiceData = $this->invoiceService->allData($school->id); $invoiceData = $this->invoiceService->allData($school->id);
@ -179,8 +131,9 @@ class SchoolController extends Controller
if ($user->school_id !== $school->id) { if ($user->school_id !== $school->id) {
return redirect()->back()->with('error', 'That user is not at that school'); return redirect()->back()->with('error', 'That user is not at that school');
} }
/** @noinspection PhpUnhandledExceptionInspection */
$headSetter->setHeadDirector($user); $headSetter->setHeadDirector($user);
return redirect()->back()->with('success', 'Head director set'); return redirect()->back()->with('success', 'Head director set successfully.');
} }
} }

View File

@ -4,13 +4,21 @@ namespace App\Http\Requests;
use App\Models\School; use App\Models\School;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class SchoolStoreRequest extends FormRequest class SchoolStoreRequest extends FormRequest
{ {
public function rules(): array public function rules(): array
{ {
$schoolId = $this->route('school');
return [ return [
'name' => ['required', 'min:3', 'max:30', 'unique:schools,name'], 'name' => [
'required',
'min:3',
'max:30',
Rule::unique('schools', 'name')->ignore($schoolId),
],
'address' => ['required'], 'address' => ['required'],
'city' => ['required'], 'city' => ['required'],
'state' => ['required', 'min:2', 'max:2'], 'state' => ['required', 'min:2', 'max:2'],

View File

@ -0,0 +1,328 @@
<?php
use App\Models\School;
use App\Models\SchoolEmailDomain;
use App\Models\Student;
use App\Models\User;
use App\Services\Invoice\InvoiceDataService;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
/** @noinspection PhpUnhandledExceptionInspection */
$mockInvoice = $this->createMock(InvoiceDataService::class);
$mockInvoice->method('getGrandTotal')->willReturn(40.00);
$this->app->instance(InvoiceDataService::class, $mockInvoice);
});
describe('SchoolController::index', function () {
it('denies access to a non-admin user', function () {
$this->get(route('admin.schools.index'))
->assertRedirect(route('home'));
actAsNormal();
$this->get(route('admin.schools.index'))
->assertRedirect(route('dashboard'));
actAsTab();
$this->get(route('admin.schools.index'))
->assertRedirect(route('dashboard'));
});
it('shows all schools', function () {
School::factory()->count(3)->create();
actAsAdmin();
$response = $this->get(route('admin.schools.index'));
$response->assertOk();
foreach (School::all() as $school) {
$response->assertSee($school->name);
}
$response->assertSee('$40.00');
});
});
describe('SchoolController::show', function () {
beforeEach(function () {
$this->school = School::factory()->create();
});
it('denies access to a non-admin user', function () {
$this->get(route('admin.schools.show', $this->school))
->assertRedirect(route('home'));
actAsNormal();
$this->get(route('admin.schools.show', $this->school))
->assertRedirect(route('dashboard'));
actAsTab();
$this->get(route('admin.schools.show', $this->school))
->assertRedirect(route('dashboard'));
});
it('shows the school details', function () {
actAsAdmin();
$response = $this->get(route('admin.schools.show', $this->school));
$response->assertOk();
$response->assertSee($this->school->name);
});
});
describe('SchoolController::edit', function () {
beforeEach(function () {
$this->school = School::factory()->create();
});
it('denies access to a non-admin user', function () {
$this->get(route('admin.schools.edit', $this->school))
->assertRedirect(route('home'));
actAsNormal();
$this->get(route('admin.schools.edit', $this->school))
->assertRedirect(route('dashboard'));
actAsTab();
$this->get(route('admin.schools.edit', $this->school))
->assertRedirect(route('dashboard'));
});
it('shows the school details', function () {
actAsAdmin();
$response = $this->get(route('admin.schools.edit', $this->school));
$response->assertOk();
$response->assertSee($this->school->name);
});
});
describe('SchoolController::update', function () {
beforeEach(function () {
$this->school = School::create([
'name' => 'Test School',
'address' => '123 4th street',
'city' => 'New York',
'state' => 'NY',
'zip' => '10001',
]);
$this->newData = [
'name' => 'New Name',
'address' => '567 8th street',
'city' => 'New Orleans',
'state' => 'LA',
'zip' => '70110',
];
});
it('denies access to a non-admin user', function () {
$this->patch(route('admin.schools.update', $this->school), $this->newData)
->assertRedirect(route('home'));
actAsNormal();
$this->patch(route('admin.schools.update', $this->school), $this->newData)
->assertRedirect(route('dashboard'));
actAsTab();
$this->patch(route('admin.schools.update', $this->school), $this->newData)
->assertRedirect(route('dashboard'));
});
it('updates the school', function () {
actAsAdmin();
$this->patch(route('admin.schools.update', $this->school), $this->newData);
$this->school->refresh();
$this->assertEquals($this->newData['name'], $this->newData['name']);
$this->assertEquals($this->newData['address'], $this->newData['address']);
$this->assertEquals($this->newData['city'], $this->newData['city']);
$this->assertEquals($this->newData['state'], $this->newData['state']);
$this->assertEquals($this->newData['zip'], $this->newData['zip']);
});
it('will not let us duplicate a name', function () {
$otherSchool = School::factory()->create();
$this->newData['name'] = $otherSchool->name;
actAsAdmin();
$response = $this->patch(route('admin.schools.update', $this->school), $this->newData);
$response->assertSessionHasErrors('name');
$this->school->refresh();
$this->assertNotEquals($this->school->name, $otherSchool->name);
});
it('will let us update other data and retain the name', function () {
$this->newData['name'] = 'Test School';
actAsAdmin();
$response = $this->patch(route('admin.schools.update', $this->school), $this->newData);
/** @noinspection PhpUnhandledExceptionInspection */
$response->assertSessionHasNoErrors();
$this->school->refresh();
$this->assertEquals($this->newData['address'], $this->newData['address']);
});
});
describe('SchoolController::create', function () {
it('denies access to a non-admin user', function () {
$this->get(route('admin.schools.create'))
->assertRedirect(route('home'));
actAsNormal();
$this->get(route('admin.schools.create'))
->assertRedirect(route('dashboard'));
actAsTab();
$this->get(route('admin.schools.create'))
->assertRedirect(route('dashboard'));
});
it('shows the school creation form', function () {
actAsAdmin();
$response = $this->get(route('admin.schools.create'));
$response->assertOk();
$response->assertSee('Create School');
});
});
describe('SchoolController::store', function () {
beforeEach(function () {
$this->newData = [
'name' => 'Test School',
'address' => '123 4th street',
'city' => 'New York',
'state' => 'NY',
'zip' => '10001',
];
});
it('denies access to a non-admin user', function () {
$this->post(route('admin.schools.store'), $this->newData)
->assertRedirect(route('home'));
actAsNormal();
$this->post(route('admin.schools.store'), $this->newData)
->assertRedirect(route('dashboard'));
actAsTab();
$this->post(route('admin.schools.store'), $this->newData)
->assertRedirect(route('dashboard'));
});
it('creates a new school', function () {
actAsAdmin();
/** @noinspection PhpUnhandledExceptionInspection */
$this->post(route('admin.schools.store'),
$this->newData)->assertRedirect(route('admin.schools.index'))->assertSessionHasNoErrors();
$newSchool = School::first();
expect($newSchool)->toBeInstanceOf(School::class)
->and($newSchool->name)->toEqual($this->newData['name'])
->and($newSchool->address)->toEqual($this->newData['address'])
->and($newSchool->city)->toEqual($this->newData['city'])
->and($newSchool->state)->toEqual($this->newData['state'])
->and($newSchool->zip)->toEqual($this->newData['zip']);
});
it('will not let us duplicate a name', function () {
$otherSchool = School::factory()->create();
$this->newData['name'] = $otherSchool->name;
actAsAdmin();
$this->post(route('admin.schools.store'), $this->newData)->assertSessionHasErrors('name');
$this->assertEquals(School::count(), 1);
});
});
describe('SchoolController::destroy', function () {
beforeEach(function () {
$this->school = School::factory()->create();
});
it('denies access to a non-admin user', function () {
$this->delete(route('admin.schools.destroy', $this->school))
->assertRedirect(route('home'));
actAsNormal();
$this->delete(route('admin.schools.destroy', $this->school))
->assertRedirect(route('dashboard'));
actAsTab();
$this->delete(route('admin.schools.destroy', $this->school))
->assertRedirect(route('dashboard'));
});
it('deletes a school', function () {
actAsAdmin();
$this->delete(route('admin.schools.destroy', $this->school))->assertRedirect(route('admin.schools.index'));
expect(School::count())->toEqual(0);
});
it('will not delete a school with students', function () {
Student::factory()->forSchool($this->school)->create();
actAsAdmin();
$response = $this->delete(route('admin.schools.destroy', $this->school));
$response->assertRedirect(route('admin.schools.index'))
->assertSessionHas('error', 'You cannot delete a school that has students.');
expect(School::count())->toEqual(1);
});
});
describe('SchoolController::add_domain', function () {
beforeEach(function () {
$this->school = School::factory()->create();
});
it('denies access to a non-admin user', function () {
$this->post(route('admin.schools.add_domain', $this->school))
->assertRedirect(route('home'));
actAsNormal();
$this->post(route('admin.schools.add_domain', $this->school))
->assertRedirect(route('dashboard'));
actAsTab();
$this->post(route('admin.schools.add_domain', $this->school))
->assertRedirect(route('dashboard'));
});
it('can add a domain to a school', function () {
actAsAdmin();
$this->post(route('admin.schools.add_domain', $this->school),
['domain' => 'test.com'])->assertRedirect(route('admin.schools.show', $this->school));
$this->school->refresh();
expect($this->school->emailDomains)->toHaveCount(1)
->and($this->school->emailDomains->first()->domain)->toEqual('test.com');
});
});
describe('SchoolController::remove_domain', function () {
beforeEach(function () {
$this->school = School::factory()->create();
$this->domain = SchoolEmailDomain::create([
'school_id' => $this->school->id,
'domain' => 'test.com',
]);
});
it('denies access to a non-admin user', function () {
$this->delete(route('admin.schools.destroy_domain', $this->domain))
->assertRedirect(route('home'));
actAsNormal();
$this->delete(route('admin.schools.destroy_domain', $this->domain))
->assertRedirect(route('dashboard'));
actAsTab();
$this->delete(route('admin.schools.destroy_domain', $this->domain))
->assertRedirect(route('dashboard'));
});
it('can remove a domain from a school', function () {
actAsAdmin();
$this->delete(route('admin.schools.destroy_domain', $this->domain))->assertSessionHas('success',
'Domain removed successfully.');
$this->school->refresh();
expect($this->school->emailDomains)->toHaveCount(0);
});
});
describe('SchoolController::setHeadDirector', function () {
beforeEach(function () {
$this->school = School::factory()->create();
$this->oldHeadDirector = User::factory()->forSchool($this->school)->create();
$this->oldHeadDirector->addFlag('head_director');
$this->newHeadDirector = User::factory()->forSchool($this->school)->create();
$this->uneployedDirector = User::factory()->create();
});
it('denies access to a non-admin user', function () {
$this->get(route('admin.schools.set_head_director', [$this->school, $this->newHeadDirector]))
->assertRedirect(route('home'));
actAsNormal();
$this->get(route('admin.schools.set_head_director', [$this->school, $this->newHeadDirector]))
->assertRedirect(route('dashboard'));
actAsTab();
$this->get(route('admin.schools.set_head_director', [$this->school, $this->newHeadDirector]))
->assertRedirect(route('dashboard'));
});
it('can set a new head director', function () {
actAsAdmin();
$this->get(route('admin.schools.set_head_director', [$this->school, $this->newHeadDirector]))
->assertSessionHas('success', 'Head director set successfully.');
$this->school->refresh();
$this->oldHeadDirector->refresh();
$this->newHeadDirector->refresh();
expect($this->oldHeadDirector->hasFlag('head_director'))->toBeFalse()
->and($this->newHeadDirector->hasFlag('head_director'))->toBeTrue();
});
it('will not promote a user a the wrong school', function () {
actAsAdmin();
$this->get(route('admin.schools.set_head_director', [$this->school, $this->uneployedDirector]))
->assertSessionHas('error', 'That user is not at that school');
$this->school->refresh();
$this->oldHeadDirector->refresh();
$this->newHeadDirector->refresh();
expect($this->oldHeadDirector->hasFlag('head_director'))->toBeTrue()
->and($this->newHeadDirector->hasFlag('head_director'))->toBeFalse()
->and($this->uneployedDirector->hasFlag('head_director'))->toBeFalse();
});
});