diff --git a/app/Http/Controllers/Admin/SchoolController.php b/app/Http/Controllers/Admin/SchoolController.php
index ccd05fd..d3fbca9 100644
--- a/app/Http/Controllers/Admin/SchoolController.php
+++ b/app/Http/Controllers/Admin/SchoolController.php
@@ -5,14 +5,12 @@ namespace App\Http\Controllers\Admin;
use App\Actions\Schools\CreateSchool;
use App\Actions\Schools\SetHeadDirector;
use App\Http\Controllers\Controller;
-use App\Models\AuditLogEntry;
+use App\Http\Requests\SchoolStoreRequest;
use App\Models\School;
use App\Models\SchoolEmailDomain;
use App\Models\User;
use App\Services\Invoice\InvoiceDataService;
-use Illuminate\Support\Facades\Auth;
-use function abort;
use function redirect;
use function request;
@@ -39,46 +37,25 @@ class SchoolController extends Controller
public function show(School $school)
{
- if (! Auth::user()->is_admin) {
- abort(403);
- }
return view('admin.schools.show', ['school' => $school]);
}
public function edit(School $school)
{
- if (! Auth::user()->is_admin) {
- abort(403);
- }
$school->loadCount('students');
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([
- 'name' => request('name'),
- 'address' => request('address'),
- 'city' => request('city'),
- 'state' => request('state'),
- 'zip' => request('zip'),
- ]);
- $message = 'Modified school #'.$school->id.' - '.$school->name.' with address
'.$school->address.'
'.$school->city.', '.$school->state.' '.$school->zip;
- AuditLogEntry::create([
- 'user' => auth()->user()->email,
- 'ip_address' => request()->ip(),
- 'message' => $message,
- 'affected' => ['schools' => [$school->id]],
+ 'name' => $request['name'],
+ 'address' => $request['address'],
+ 'city' => $request['city'],
+ 'state' => $request['state'],
+ 'zip' => $request['zip'],
]);
return redirect()->route('admin.schools.show', ['school' => $school->id])->with('success',
@@ -87,48 +64,30 @@ class SchoolController extends Controller
public function create()
{
- if (! Auth::user()->is_admin) {
- abort(403);
- }
-
return view('admin.schools.create');
}
- public function store()
+ public function store(SchoolStoreRequest $request)
{
$creator = app(CreateSchool::class);
- $validData = request()->validate([
- 'name' => ['required', 'unique:schools,name'],
- 'address' => ['required'],
- 'city' => ['required'],
- 'state' => ['required'],
- 'zip' => ['required'],
- ]);
$school = $creator(
- $validData['name'],
- $validData['address'],
- $validData['city'],
- $validData['state'],
- $validData['zip'],
+ $request['name'],
+ $request['address'],
+ $request['city'],
+ $request['state'],
+ $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)
{
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();
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)
{
- if (! Auth::user()->is_admin) {
- abort(403);
- }
request()->validate([
// validate that the combination of school and domain is unique on the school_email_domains table
'domain' => ['required'],
@@ -147,12 +103,6 @@ class SchoolController extends Controller
'school_id' => $school->id,
'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');
@@ -164,9 +114,11 @@ class SchoolController extends Controller
$domain->delete();
// 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)
{
$invoiceData = $this->invoiceService->allData($school->id);
@@ -179,8 +131,9 @@ class SchoolController extends Controller
if ($user->school_id !== $school->id) {
return redirect()->back()->with('error', 'That user is not at that school');
}
+ /** @noinspection PhpUnhandledExceptionInspection */
$headSetter->setHeadDirector($user);
- return redirect()->back()->with('success', 'Head director set');
+ return redirect()->back()->with('success', 'Head director set successfully.');
}
}
diff --git a/app/Http/Requests/SchoolStoreRequest.php b/app/Http/Requests/SchoolStoreRequest.php
index e0dd2ea..10c936e 100644
--- a/app/Http/Requests/SchoolStoreRequest.php
+++ b/app/Http/Requests/SchoolStoreRequest.php
@@ -4,13 +4,21 @@ namespace App\Http\Requests;
use App\Models\School;
use Illuminate\Foundation\Http\FormRequest;
+use Illuminate\Validation\Rule;
class SchoolStoreRequest extends FormRequest
{
public function rules(): array
{
+ $schoolId = $this->route('school');
+
return [
- 'name' => ['required', 'min:3', 'max:30', 'unique:schools,name'],
+ 'name' => [
+ 'required',
+ 'min:3',
+ 'max:30',
+ Rule::unique('schools', 'name')->ignore($schoolId),
+ ],
'address' => ['required'],
'city' => ['required'],
'state' => ['required', 'min:2', 'max:2'],
diff --git a/tests/Feature/app/Http/Controllers/Admin/SchoolControllerTest.php b/tests/Feature/app/Http/Controllers/Admin/SchoolControllerTest.php
new file mode 100644
index 0000000..ae647c5
--- /dev/null
+++ b/tests/Feature/app/Http/Controllers/Admin/SchoolControllerTest.php
@@ -0,0 +1,328 @@
+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();
+ });
+});