247 lines
7.3 KiB
PHP
247 lines
7.3 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Studio\Finance\Expense;
|
|
use App\Models\Employee;
|
|
use App\Models\Expense as ExpenseModel;
|
|
use App\Models\Outlet;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\UploadedFile;
|
|
use Livewire\Livewire;
|
|
use Spatie\Permission\Models\Permission;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
// Setup Permissions
|
|
Permission::create(['name' => 'create expense']);
|
|
Permission::create(['name' => 'update expense']);
|
|
Permission::create(['name' => 'delete expense']);
|
|
});
|
|
|
|
function createUserWithOutlet(bool $multipleOutlets = false)
|
|
{
|
|
$user = User::factory()
|
|
->has(Employee::factory(), 'employee')
|
|
->create();
|
|
|
|
$outlet1 = Outlet::factory()->create();
|
|
$user->outlets()->attach($outlet1);
|
|
|
|
if ($multipleOutlets) {
|
|
$outlet2 = Outlet::factory()->create();
|
|
$user->outlets()->attach($outlet2);
|
|
}
|
|
|
|
// Assign permissions
|
|
$user->givePermissionTo(['create expense', 'update expense', 'delete expense']);
|
|
|
|
return [$user, $outlet1];
|
|
}
|
|
|
|
it('renders successfully', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test(Expense::class)
|
|
->assertStatus(200);
|
|
});
|
|
|
|
it('validates required fields', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test(Expense::class)
|
|
->set('form.description', '')
|
|
->set('form.amount', '')
|
|
->call('create')
|
|
->assertHasErrors(['form.description', 'form.amount']);
|
|
});
|
|
|
|
it('validates amount must be numeric', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test(Expense::class)
|
|
->set('form.amount', 'abc')
|
|
->call('create')
|
|
->assertHasErrors(['form.amount']);
|
|
});
|
|
|
|
it('validates outlet_id is required for multi-outlet users', function () {
|
|
[$user, $outlet] = createUserWithOutlet(true); // Multiple outlets
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test(Expense::class)
|
|
->set('form.description', 'Test Expense')
|
|
->set('form.amount', '50000')
|
|
// outlet_id not set
|
|
->call('create')
|
|
->assertHasErrors(['form.outlet_id']);
|
|
});
|
|
|
|
it('does not require manually selecting outlet_id for single-outlet users', function () {
|
|
[$user, $outlet] = createUserWithOutlet(false);
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test(Expense::class)
|
|
->set('form.description', 'Test Expense')
|
|
->set('form.amount', '50000')
|
|
->call('create')
|
|
->assertHasNoErrors(['form.outlet_id']);
|
|
});
|
|
|
|
it('can create an expense', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test(Expense::class)
|
|
->set('form.description', 'Buy Coffee')
|
|
->set('form.amount', '25000')
|
|
->call('create')
|
|
->assertDispatched('refreshDatatable');
|
|
|
|
$this->assertDatabaseHas('expenses', [
|
|
'description' => 'Buy Coffee',
|
|
'amount' => 25000,
|
|
'user_id' => $user->id,
|
|
'outlet_id' => $outlet->id,
|
|
]);
|
|
});
|
|
|
|
it('can create an expense with image', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$this->actingAs($user);
|
|
|
|
$image = UploadedFile::fake()->image('receipt.jpg');
|
|
// The WithMediaHandler contract seems to expect an array with 'path', not raw file object
|
|
$imageData = [['path' => $image->getPathname()]];
|
|
|
|
Livewire::test(Expense::class)
|
|
->set('form.description', 'Buy Coffee with Receipt')
|
|
->set('form.amount', '30000')
|
|
->set('form.image', $imageData)
|
|
->call('create');
|
|
|
|
$expense = ExpenseModel::where('description', 'Buy Coffee with Receipt')->first();
|
|
expect($expense)->not->toBeNull();
|
|
// Assuming media library is set up, verify media count or existence
|
|
// Note: In-memory/testing sqlite might behave differently with media library if not fully mocked,
|
|
// but basic relation check is good.
|
|
expect($expense->getMedia('image'))->toHaveCount(1);
|
|
});
|
|
|
|
it('can update an expense', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$this->actingAs($user);
|
|
|
|
$expense = ExpenseModel::factory()->create([
|
|
'user_id' => $user->id,
|
|
'outlet_id' => $outlet->id,
|
|
'description' => 'Old Description',
|
|
'amount' => 10000,
|
|
]);
|
|
|
|
Livewire::test(Expense::class)
|
|
->call('openModal', 'update', 'Edit Expense', $expense->hash)
|
|
->assertSet('form.description', 'Old Description')
|
|
->set('form.description', 'New Description')
|
|
->set('form.amount', '15000') // Updating amount
|
|
->call('update')
|
|
->assertDispatched('refreshDatatable');
|
|
|
|
$this->assertDatabaseHas('expenses', [
|
|
'id' => $expense->id,
|
|
'description' => 'New Description',
|
|
'amount' => 15000,
|
|
]);
|
|
});
|
|
|
|
it('can update an expense image', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$this->actingAs($user);
|
|
|
|
$expense = ExpenseModel::factory()->create([
|
|
'user_id' => $user->id,
|
|
'outlet_id' => $outlet->id,
|
|
'description' => 'Expense with Image',
|
|
'amount' => 10000,
|
|
]);
|
|
|
|
// Initial image upload (simulated via factory or direct DB check if needed,
|
|
// but here we just test REPLACING or ADDING via update)
|
|
|
|
$newImage = UploadedFile::fake()->image('new_receipt.jpg');
|
|
$imageData = [['path' => $newImage->getPathname()]];
|
|
|
|
Livewire::test(Expense::class)
|
|
->call('openModal', 'update', 'Edit Expense', $expense->hash)
|
|
->set('form.image', $imageData)
|
|
->call('update');
|
|
|
|
$expense->refresh();
|
|
expect($expense->getMedia('image'))->toHaveCount(1);
|
|
// In a real scenario we'd check filename, but hash names make it tricky in tests without mocking 'fake'
|
|
});
|
|
|
|
it('can delete an expense', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$this->actingAs($user);
|
|
|
|
$expense = ExpenseModel::factory()->create([
|
|
'user_id' => $user->id,
|
|
'outlet_id' => $outlet->id,
|
|
]);
|
|
|
|
Livewire::test(Expense::class)
|
|
->call('delete', $expense->hash)
|
|
->assertDispatched('refreshDatatable');
|
|
|
|
$this->assertSoftDeleted('expenses', [
|
|
'id' => $expense->id,
|
|
]);
|
|
});
|
|
|
|
// AUTHORIZATION TESTS
|
|
|
|
it('checks authorization for create', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$user->revokePermissionTo('create expense');
|
|
$this->actingAs($user);
|
|
|
|
Livewire::test(Expense::class)
|
|
->call('create')
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('checks authorization for update', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$user->revokePermissionTo('update expense');
|
|
$this->actingAs($user);
|
|
|
|
$expense = ExpenseModel::factory()->create([
|
|
'user_id' => $user->id,
|
|
'outlet_id' => $outlet->id,
|
|
]);
|
|
|
|
Livewire::test(Expense::class)
|
|
->call('openModal', 'update', 'Edit', $expense->hash) // openModal doesn't check perm, update() does
|
|
->call('update')
|
|
->assertForbidden();
|
|
});
|
|
|
|
it('checks authorization for delete', function () {
|
|
[$user, $outlet] = createUserWithOutlet();
|
|
$user->revokePermissionTo('delete expense');
|
|
$this->actingAs($user);
|
|
|
|
$expense = ExpenseModel::factory()->create([
|
|
'user_id' => $user->id,
|
|
'outlet_id' => $outlet->id,
|
|
]);
|
|
|
|
Livewire::test(Expense::class)
|
|
->call('delete', $expense->hash)
|
|
->assertForbidden();
|
|
});
|