parfum/tests/Feature/Livewire/Studio/Finance/ExpenseTest.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();
});