85 lines
3.8 KiB
PHP
85 lines
3.8 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Admin\Settings;
|
|
use App\Models\Customer;
|
|
use App\Models\Operator;
|
|
use App\Models\User;
|
|
use Livewire\Livewire;
|
|
|
|
// operator() helper defined in RbacTest.
|
|
|
|
it('updates the operator own account', function () {
|
|
$owner = operator('Owner');
|
|
|
|
Livewire::actingAs($owner, 'operator')->test(Settings::class)
|
|
->set('name', 'New Name')->set('email', 'new@ops.test')->call('saveAccount')->assertHasNoErrors();
|
|
|
|
expect($owner->fresh()->name)->toBe('New Name')->and($owner->fresh()->email)->toBe('new@ops.test');
|
|
});
|
|
|
|
it('lets an Owner invite staff with a role', function () {
|
|
Livewire::actingAs(operator('Owner'), 'operator')->test(Settings::class)
|
|
->set('staffName', 'Sam')->set('staffEmail', 'sam@ops.test')->set('staffRole', 'Support')
|
|
->call('inviteStaff')->assertHasNoErrors();
|
|
|
|
$sam = Operator::query()->where('email', 'sam@ops.test')->first();
|
|
expect($sam)->not->toBeNull()->and($sam->hasRole('Support'))->toBeTrue();
|
|
});
|
|
|
|
it('surfaces a usable temporary password for the invited staff', function () {
|
|
$c = Livewire::actingAs(operator('Owner'), 'operator')->test(Settings::class)
|
|
->set('staffName', 'Ivy')->set('staffEmail', 'ivy@ops.test')->set('staffRole', 'Support')
|
|
->call('inviteStaff');
|
|
|
|
$temp = $c->get('invitedPassword');
|
|
expect($temp)->not->toBeNull();
|
|
$ivy = Operator::query()->where('email', 'ivy@ops.test')->first();
|
|
expect(Illuminate\Support\Facades\Hash::check($temp, $ivy->password))->toBeTrue();
|
|
});
|
|
|
|
it('forbids a non-Owner from managing staff', function () {
|
|
Livewire::actingAs(operator('Admin'), 'operator')->test(Settings::class)
|
|
->set('staffName', 'X')->set('staffEmail', 'x@ops.test')->set('staffRole', 'Support')
|
|
->call('inviteStaff')->assertForbidden();
|
|
expect(Operator::query()->where('email', 'x@ops.test')->exists())->toBeFalse();
|
|
});
|
|
|
|
it('will not create an operator from a customer email', function () {
|
|
Customer::factory()->create(['email' => 'dup@ops.test']);
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')->test(Settings::class)
|
|
->set('staffName', 'Dup')->set('staffEmail', 'dup@ops.test')->set('staffRole', 'Support')
|
|
->call('inviteStaff')->assertHasErrors(['staffEmail']);
|
|
});
|
|
|
|
it('will not escalate a non-staff user via a tampered id', function () {
|
|
// The old risk was one table shared by both groups: a customer's numeric
|
|
// id could be handed to setStaffRole() and land on that same row. That is
|
|
// structurally closed now — setStaffRole() only ever queries `operators`,
|
|
// a table this customer is not in — so the regression to guard against is
|
|
// that no operator anywhere picks up the 'Admin' role as a side effect.
|
|
$adminsBefore = Operator::role('Admin')->count();
|
|
$victim = User::factory()->create(); // a plain (customer) user, no role
|
|
|
|
Livewire::actingAs(operator('Owner'), 'operator')->test(Settings::class)->call('setStaffRole', $victim->id, 'Admin');
|
|
|
|
expect(Operator::role('Admin')->count())->toBe($adminsBefore);
|
|
});
|
|
|
|
it('protects the last owner and self-role', function () {
|
|
$owner = operator('Owner');
|
|
$support = operator('Support');
|
|
|
|
// Cannot change own role.
|
|
Livewire::actingAs($owner, 'operator')->test(Settings::class)->call('setStaffRole', $owner->id, 'Admin');
|
|
expect($owner->fresh()->hasRole('Owner'))->toBeTrue();
|
|
|
|
// Only one owner → cannot revoke it.
|
|
Livewire::actingAs($owner, 'operator')->test(Settings::class)->call('revokeStaff', $owner->id);
|
|
expect($owner->fresh()->hasRole('Owner'))->toBeTrue();
|
|
|
|
// Owner can change another operator's role.
|
|
Livewire::actingAs($owner, 'operator')->test(Settings::class)->call('setStaffRole', $support->id, 'Billing');
|
|
expect($support->fresh()->hasRole('Billing'))->toBeTrue()->and($support->fresh()->hasRole('Support'))->toBeFalse();
|
|
});
|