School controller refactoring and testing.

This commit is contained in:
Matt Young 2025-07-05 02:54:27 -05:00
parent f3b2372682
commit d9688fd3b0
4 changed files with 235 additions and 62 deletions

View File

@ -53,6 +53,7 @@ class CreateNewUser implements CreatesNewUsers
'password' => Hash::make($input['password']), 'password' => Hash::make($input['password']),
]); ]);
// TODO: Move logging to observer
$message = 'New User Registered - '.$input['email'] $message = 'New User Registered - '.$input['email']
.'<br>Name: '.$input['first_name'].' '.$input['last_name'] .'<br>Name: '.$input['first_name'].' '.$input['last_name']
.'<br>Judging Pref: '.$input['judging_preference'] .'<br>Judging Pref: '.$input['judging_preference']

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers; namespace App\Http\Controllers;
use App\Actions\Fortify\CreateNewUser;
use App\Actions\Schools\AddSchoolEmailDomain;
use App\Actions\Schools\AssignUserToSchool; use App\Actions\Schools\AssignUserToSchool;
use App\Actions\Schools\CreateSchool; use App\Actions\Schools\CreateSchool;
use App\Actions\Schools\SetHeadDirector; use App\Actions\Schools\SetHeadDirector;
@ -12,15 +14,12 @@ use App\Models\AuditLogEntry;
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 Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use function abort; use function abort;
use function auditionLog;
use function redirect; use function redirect;
use function request; use function request;
@ -97,52 +96,38 @@ class SchoolController extends Controller
public function addDirector(School $school) public function addDirector(School $school)
{ {
if (auth()->user()->school_id !== $school->id) { if (auth()->user()->school_id !== $school->id) {
return redirect()->back()->with('error', 'No adding directors to another school'); abort(403);
} }
if (! auth()->user()->hasFlag('head_director')) { if (! auth()->user()->hasFlag('head_director')) {
return redirect()->back()->with('error', 'Only the head director can add directors to a school'); abort(403);
} }
$validData = request()->validate([
'first_name' => ['required'], $userCreator = app(CreateNewUser::class);
'last_name' => ['required'],
'email' => ['required', 'email', 'unique:users'],
'cell_phone' => ['required'],
'judging_preference' => ['required'],
]);
// Generate a random password
$randomPassword = Str::random(12); $randomPassword = Str::random(12);
$newUser = User::create([ $data = request()->all();
'first_name' => $validData['first_name'], $data['password'] = $randomPassword;
'last_name' => $validData['last_name'], $data['password_confirmation'] = $randomPassword;
'email' => $validData['email'], $newDirector = $userCreator->create($data);
'cell_phone' => $validData['cell_phone'], $newDirector->update([
'judging_preference' => $validData['judging_preference'], 'school_id' => $school->id,
'password' => Hash::make($randomPassword),
'school_id' => auth()->user()->school_id,
]); ]);
$logMessage = 'Created user '.$newUser->full_name().' - '.$newUser->email.' as a director at '.$newUser->school->name;
$logAffected = ['users' => [$newUser->id], 'schools' => [$newUser->school_id]]; Mail::to($newDirector->email)->send(new NewUserPassword($newDirector, $randomPassword));
auditionLog($logMessage, $logAffected);
Mail::to($newUser->email)->send(new NewUserPassword($newUser, $randomPassword));
return redirect()->back()->with('success', 'Director added'); return redirect()->back()->with('success', 'Director added');
} }
public function setHeadDirector( public function setHeadDirector(School $school, User $user, SetHeadDirector $headSetter)
School $school, {
User $user,
SetHeadDirector $headSetter
) {
if (auth()->user()->school_id !== $school->id) { if (auth()->user()->school_id !== $school->id) {
return redirect()->back()->with('error', 'No setting the head director for another school'); abort(403);
} }
if (! auth()->user()->hasFlag('head_director')) { if (! auth()->user()->hasFlag('head_director')) {
return redirect()->back()->with('error', 'Only the head director can name a new head director'); abort(403);
} }
if ($school->id !== $user->school_id) { if ($school->id !== $user->school_id) {
return redirect()->back()->with('error', 'The proposed head director must be at your school'); abort(403);
} }
try { try {
$headSetter->setHeadDirector($user); $headSetter->setHeadDirector($user);
@ -150,50 +135,38 @@ class SchoolController extends Controller
return redirect()->back()->with('error', $e->getMessage()); return redirect()->back()->with('error', $e->getMessage());
} }
return redirect()->back()->with('success', 'New head director set'); return redirect()->route('schools.show', $school)->with('success', 'New head director set');
} }
public function addDomain( public function addDomain(School $school)
School $school {
) {
if (auth()->user()->school_id !== $school->id) { if (auth()->user()->school_id !== $school->id) {
return redirect()->back()->with('error', 'No adding domains for another school'); abort(403);
} }
if (! auth()->user()->hasFlag('head_director')) { if (! auth()->user()->hasFlag('head_director')) {
return redirect()->back()->with('error', 'Only the head director can add domains'); abort(403);
} }
$verifiedData = request()->validate([ $verifiedData = request()->validate([
'domain' => ['required'], 'domain' => ['required'],
]); ]);
try { app(AddSchoolEmailDomain::class)->addDomain($school, $verifiedData['domain']);
SchoolEmailDomain::create([
'school_id' => $school->id,
'domain' => $verifiedData['domain'],
]);
} catch (UniqueConstraintViolationException $e) {
return redirect()->back()->with('error', 'That domain is already associated with your school');
}
$logMessage = 'Added domain '.$verifiedData['domain'].' to school '.$school->name;
$logAffected = ['schools' => [$school->id]];
auditionLog($logMessage, $logAffected);
return redirect()->back()->with('success', 'Domain added'); return redirect()->route('schools.show', $school)->with('success', 'Domain added');
} }
public function deleteDomain( public function deleteDomain(SchoolEmailDomain $domain)
SchoolEmailDomain $domain {
) {
if (auth()->user()->school_id !== $domain->school_id) { if (auth()->user()->school_id !== $domain->school_id) {
return redirect()->back()->with('error', 'No deleting domains for another school'); abort(403);
} }
if (! auth()->user()->hasFlag('head_director')) { if (! auth()->user()->hasFlag('head_director')) {
return redirect()->back()->with('error', 'Only the head director can delete domains'); abort(403);
} }
$logMessage = 'Deleted domain '.$domain->domain.' from school '.$domain->school->name;
$logAffected = ['schools' => [$domain->school_id]];
auditionLog($logMessage, $logAffected);
$domain->delete(); $domain->delete();
return redirect()->back()->with('success', 'Domain deleted'); return redirect()
->route('schools.show', auth()->user()->school)
->with('success', 'Domain deleted');
} }
} }

View File

@ -18,6 +18,10 @@ class ValidRegistrationCode implements ValidationRule
*/ */
public function validate(string $attribute, mixed $value, Closure $fail): void public function validate(string $attribute, mixed $value, Closure $fail): void
{ {
if (auth()->check()) {
return; // Skip validation for authenticated users
}
if ($value !== Settings::get('registrationCode')) { if ($value !== Settings::get('registrationCode')) {
$fail('Incorrect registration code provided'); $fail('Incorrect registration code provided');
} }

View File

@ -1,6 +1,7 @@
<?php <?php
use App\Models\School; use App\Models\School;
use App\Models\SchoolEmailDomain;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
@ -186,5 +187,199 @@ describe('update method', function () {
->assertRedirect(route('schools.show', $school)); ->assertRedirect(route('schools.show', $school));
expect($school->name === 'Eastman')->toBeFalse(); expect($school->name === 'Eastman')->toBeFalse();
}); });
});
describe('addDirector method', function () {
it('will not all the user to add users to another school', function () {
$user = User::factory()->create();
$school = School::factory()->create();
$user->school_id = $school->id;
$user->save();
actingAs($user);
$otherSchool = School::factory()->create();
$response = $this->post(route('schools.add_director', $otherSchool));
$response->assertStatus(403);
});
it('will only add users for the head director role', function () {
$user = User::factory()->create();
$school = School::factory()->create();
$user->school_id = $school->id;
$user->save();
actingAs($user);
$response = $this->post(route('schools.add_director', $school));
$response->assertStatus(403);
});
it('will add a user to the school for the head director', function () {
Mail::fake();
$user = User::factory()->create();
$school = School::factory()->create();
$user->school_id = $school->id;
$user->save();
$user->addFlag('head_director');
actingAs($user);
expect(User::count())->toEqual(1);
$response = $this->post(route('schools.add_director', $school), [
'first_name' => 'Jean Luc',
'last_name' => 'Picard',
'email' => 'j.picard@starfleet.com',
'cell_phone' => '1701',
'judging_preference' => 'Shut up Wesley',
]);
expect(User::count())->toEqual(2);
Mail::assertSentCount(1);
});
});
describe('setHeadDirector method', function () {
it('will not allow a director to alter another school', function () {
$school = School::factory()->create();
$user = User::factory()->create();
$otherUser = User::factory()->create();
$user->school_id = $school->id;
$user->save();
$otherUser->school_id = $school->id;
$otherUser->save();
$user->addFlag('head_director');
actingAs($user);
$otherSchool = School::factory()->create();
$response = $this->get(route('schools.set_head_director', [$otherSchool, $otherUser]));
$response->assertStatus(403);
});
it('only a head director can replace themselves', function () {
$school = School::factory()->create();
$user = User::factory()->create();
$otherUser = User::factory()->create();
$user->school_id = $school->id;
$user->save();
$otherUser->school_id = $school->id;
$otherUser->save();
actingAs($user);
$response = $this->get(route('schools.set_head_director', [$school, $otherUser]));
$response->assertStatus(403);
});
it('you can only promote users at your school', function () {
$school = School::factory()->create();
$user = User::factory()->create();
$otherUser = User::factory()->create();
$user->school_id = $school->id;
$user->save();
$user->addFlag('head_director');
actingAs($user);
$response = $this->get(route('schools.set_head_director', [$school, $otherUser]));
$response->assertStatus(403);
});
it('promotes another director', function () {
$school = School::factory()->create();
$user = User::factory()->create();
$otherUser = User::factory()->create();
$user->school_id = $school->id;
$user->save();
$otherUser->school_id = $school->id;
$otherUser->save();
$user->addFlag('head_director');
actingAs($user);
$response = $this->get(route('schools.set_head_director', [$school, $otherUser]));
$response->assertRedirect(route('schools.show', $school))
->assertSessionHas('success');
$user->refresh();
$otherUser->refresh();
expect($user->hasFlag('head_director'))->toBeFalse();
expect($otherUser->hasFlag('head_director'))->toBeTrue();
// TODO: Mock the promoter action so we can test the exception handling
});
});
describe('addDomain method', function () {
it('will not allow a user to add a domain to a school they are not a member of', function () {
$user = User::factory()->create();
$school = School::factory()->create();
$user->school_id = $school->id;
$user->save();
actingAs($user);
$otherSchool = School::factory()->create();
$response = $this->post(route('schools.add_domain', $otherSchool));
$response->assertStatus(403);
});
it('will only allow a head director to add a domain', function () {
$user = User::factory()->create();
$school = School::factory()->create();
$user->school_id = $school->id;
$user->save();
actingAs($user);
$response = $this->post(route('schools.add_domain', $school));
$response->assertStatus(403);
});
it('will add a domain to the school', function () {
$user = User::factory()->create();
$school = School::factory()->create();
$user->school_id = $school->id;
$user->save();
$user->addFlag('head_director');
$user->refresh();
actingAs($user);
$response = $this->post(route('schools.add_domain', $school), [
'domain' => 'test.com',
]);
$response->assertRedirect(route('schools.show', $school))
->assertSessionHas('success');
$school->refresh();
expect(SchoolEmailDomain::where('domain', 'test.com')
->where('school_id', $school->id)
->exists())->toBeTrue();
});
});
describe('removeDomain method', function () {
it('will not allow a user to remove a domain from a school they are not a member of', function () {
$user = User::factory()->create();
$school = School::factory()->create();
$user->school_id = $school->id;
$user->save();
actingAs($user);
$otherSchool = School::factory()->create();
$domain = SchoolEmailDomain::create([
'school_id' => $otherSchool->id,
'domain' => 'test.com',
]);
$response = $this->get(route('schools.delete_domain', $domain));
$response->assertStatus(403);
});
it('will only allow a head director to remove a domain', function () {
$user = User::factory()->create();
$school = School::factory()->create();
$user->school_id = $school->id;
$user->save();
actingAs($user);
$domain = SchoolEmailDomain::create([
'school_id' => $school->id,
'domain' => 'test.com',
]);
$response = $this->get(route('schools.delete_domain', $domain));
$response->assertStatus(403);
});
it('will remove a domain from the school', function () {
$user = User::factory()->create();
$school = School::factory()->create();
$user->school_id = $school->id;
$user->save();
$user->addFlag('head_director');
$domain = SchoolEmailDomain::create([
'school_id' => $school->id,
'domain' => 'test.com',
]);
actingAs($user);
$response = $this->get(route('schools.delete_domain', $domain));
$response->assertRedirect(route('schools.show', $school))
->assertSessionHas('success');
$school->refresh();
expect(SchoolEmailDomain::where('domain', 'test.com')
->where('school_id', $school->id)
->exists())->toBeFalse();
});
}); });