317 lines
9.9 KiB
PHP
317 lines
9.9 KiB
PHP
<?php
|
|
|
|
use App\Models\Product;
|
|
use App\Models\Purchase;
|
|
use App\Models\PurchaseItem;
|
|
|
|
use function Pest\Laravel\actingAs;
|
|
use function Pest\Laravel\assertDatabaseHas;
|
|
use function Pest\Laravel\assertSoftDeleted;
|
|
use function Pest\Laravel\deleteJson;
|
|
use function Pest\Laravel\get;
|
|
use function Pest\Laravel\patchJson;
|
|
use function Pest\Laravel\postJson;
|
|
|
|
/*
|
|
|--------------------------------------------------------------------------
|
|
| Purchase Module Tests
|
|
|--------------------------------------------------------------------------
|
|
*/
|
|
|
|
describe('Purchase Module - Authorization', function () {
|
|
it('redirects to login when accessing purchase index unauthenticated', function () {
|
|
get(route('purchase.index'))
|
|
->assertRedirect(route('login'));
|
|
});
|
|
|
|
it('returns 403 when user has no permission to view purchases', function () {
|
|
actingAs(createUnauthorizedUser())
|
|
->get(route('purchase.index'))
|
|
->assertStatus(403);
|
|
});
|
|
});
|
|
|
|
describe('Purchase Module - Authorized Actions', function () {
|
|
beforeEach(function () {
|
|
$user = createAuthorizedUser([
|
|
'View:Purchase',
|
|
'Create:Purchase',
|
|
'Edit:Purchase',
|
|
'Delete:Purchase',
|
|
'DeleteAny:Purchase',
|
|
]);
|
|
actingAs($user);
|
|
});
|
|
|
|
it('can access purchase index page', function () {
|
|
get(route('purchase.index'))
|
|
->assertOk()
|
|
->assertInertia(fn ($page) => $page
|
|
->component('admin/manage/purchase/index')
|
|
->has('purchases', fn ($page) => $page
|
|
->has('data')
|
|
->etc()
|
|
)
|
|
);
|
|
});
|
|
|
|
it('can access purchase create page', function () {
|
|
get(route('purchase.create'))
|
|
->assertOk()
|
|
->assertInertia(fn ($page) => $page
|
|
->component('admin/manage/purchase/create')
|
|
->has('products')
|
|
->has('cartItems')
|
|
);
|
|
});
|
|
|
|
it('can manage cart items', function () {
|
|
$product = Product::factory()->create();
|
|
|
|
// 1. Add to cart
|
|
postJson(route('purchase.addToCart'), [
|
|
'product_id' => $product->id,
|
|
'quantity' => 5,
|
|
'unit_price' => 5000,
|
|
])->assertRedirect();
|
|
|
|
assertDatabaseHas('purchase_items', [
|
|
'user_id' => auth()->id(),
|
|
'purchase_id' => null,
|
|
'product_id' => $product->id,
|
|
'quantity' => 5,
|
|
]);
|
|
|
|
$cartItem = PurchaseItem::where('product_id', $product->id)->whereNull('purchase_id')->first();
|
|
|
|
// 2. Update cart item
|
|
patchJson(route('purchase.updateCartItem', $cartItem), [
|
|
'product_id' => $product->id,
|
|
'quantity' => 10,
|
|
'unit_price' => 5000,
|
|
])->assertRedirect();
|
|
|
|
expect($cartItem->fresh()->quantity)->toBe(10);
|
|
|
|
// 3. Remove from cart
|
|
deleteJson(route('purchase.removeFromCart', $cartItem))->assertRedirect();
|
|
assertSoftDeleted('purchase_items', ['id' => $cartItem->id]);
|
|
});
|
|
|
|
it('can store a new purchase and increase stock', function () {
|
|
$product = Product::factory()->create(['stock' => 10]);
|
|
|
|
$data = [
|
|
'purchase_date' => now()->format('Y-m-d'),
|
|
'note' => 'Test Purchase',
|
|
'items' => [
|
|
[
|
|
'product_id' => $product->id,
|
|
'quantity' => 20,
|
|
'unit_price' => 5000,
|
|
],
|
|
],
|
|
];
|
|
|
|
postJson(route('purchase.store'), $data)
|
|
->assertRedirect(route('purchase.index'))
|
|
->assertSessionHas('success');
|
|
|
|
assertDatabaseHas('purchases', [
|
|
'note' => 'Test Purchase',
|
|
'total' => 100000,
|
|
]);
|
|
|
|
// Check stock increment (10 + 20 = 30)
|
|
expect($product->fresh()->stock)->toBe(30);
|
|
|
|
// Check purchase items
|
|
assertDatabaseHas('purchase_items', [
|
|
'product_id' => $product->id,
|
|
'quantity' => 20,
|
|
'unit_price' => 5000,
|
|
]);
|
|
});
|
|
|
|
it('validates purchase creation', function () {
|
|
postJson(route('purchase.store'), [])
|
|
->assertStatus(422)
|
|
->assertJsonValidationErrors(['purchase_date', 'items']);
|
|
});
|
|
|
|
it('returns flash error when stock is insufficient on purchase update', function () {
|
|
$product = Product::factory()->create(['stock' => 0]);
|
|
|
|
$purchase = Purchase::create([
|
|
'purchase_date' => now()->format('Y-m-d'),
|
|
'note' => 'Old Note',
|
|
'total' => 5000,
|
|
]);
|
|
|
|
$purchase->items()->create([
|
|
'user_id' => auth()->id(),
|
|
'product_id' => $product->id,
|
|
'quantity' => 5,
|
|
'unit_price' => 1000,
|
|
'total_price' => 5000,
|
|
]);
|
|
|
|
$newData = [
|
|
'purchase_date' => now()->format('Y-m-d'),
|
|
'note' => 'Updated Note',
|
|
'items' => [
|
|
[
|
|
'product_id' => $product->id,
|
|
'quantity' => 10,
|
|
'unit_price' => 2000,
|
|
],
|
|
],
|
|
];
|
|
|
|
patchJson(route('purchase.update', $purchase), $newData)
|
|
->assertRedirect()
|
|
->assertSessionHas('error');
|
|
|
|
expect($purchase->fresh()->note)->toBe('Old Note');
|
|
});
|
|
|
|
it('returns flash error when stock is insufficient on purchase delete', function () {
|
|
$product = Product::factory()->create(['stock' => 0]);
|
|
$purchase = Purchase::factory()->create();
|
|
|
|
$purchase->items()->create([
|
|
'user_id' => auth()->id(),
|
|
'product_id' => $product->id,
|
|
'quantity' => 5,
|
|
'unit_price' => 1000,
|
|
'total_price' => 5000,
|
|
]);
|
|
|
|
deleteJson(route('purchase.destroy', $purchase))
|
|
->assertRedirect()
|
|
->assertSessionHas('error');
|
|
|
|
expect(Purchase::find($purchase->id))->not->toBeNull();
|
|
});
|
|
|
|
it('can access purchase edit page', function () {
|
|
$purchase = Purchase::factory()->create();
|
|
|
|
get(route('purchase.edit', $purchase))
|
|
->assertOk()
|
|
->assertInertia(fn ($page) => $page
|
|
->component('admin/manage/purchase/edit')
|
|
->has('purchase')
|
|
->has('products')
|
|
);
|
|
});
|
|
|
|
it('can update a purchase and adjust stock', function () {
|
|
$product1 = Product::factory()->create(['stock' => 10]);
|
|
$product2 = Product::factory()->create(['stock' => 10]);
|
|
|
|
$purchase = Purchase::create([
|
|
'purchase_date' => now()->format('Y-m-d'),
|
|
'note' => 'Old Note',
|
|
'total' => 5000,
|
|
]);
|
|
|
|
$purchase->items()->create([
|
|
'user_id' => auth()->id(),
|
|
'product_id' => $product1->id,
|
|
'quantity' => 5,
|
|
'unit_price' => 1000,
|
|
'total_price' => 5000,
|
|
]);
|
|
|
|
// Manually adjust stock as if it was stored
|
|
$product1->increment('stock', 5);
|
|
|
|
$newData = [
|
|
'purchase_date' => now()->format('Y-m-d'),
|
|
'note' => 'Updated Note',
|
|
'items' => [
|
|
[
|
|
'product_id' => $product2->id,
|
|
'quantity' => 15,
|
|
'unit_price' => 2000,
|
|
],
|
|
],
|
|
];
|
|
|
|
patchJson(route('purchase.update', $purchase), $newData)
|
|
->assertRedirect(route('purchase.index'))
|
|
->assertSessionHas('success');
|
|
|
|
// Check data updated
|
|
assertDatabaseHas('purchases', [
|
|
'id' => $purchase->id,
|
|
'note' => 'Updated Note',
|
|
]);
|
|
|
|
// Check stock: product1 should be decremented back (15 - 5 = 10), product2 should be incremented (10 + 15 = 25)
|
|
expect($product1->fresh()->stock)->toBe(10);
|
|
expect($product2->fresh()->stock)->toBe(25);
|
|
});
|
|
|
|
it('can delete a purchase and decrease stock', function () {
|
|
$product = Product::factory()->create(['stock' => 20]);
|
|
$purchase = Purchase::factory()->create();
|
|
$purchase->items()->create([
|
|
'user_id' => auth()->id(),
|
|
'product_id' => $product->id,
|
|
'quantity' => 10,
|
|
'unit_price' => 1000,
|
|
'total_price' => 10000,
|
|
]);
|
|
|
|
// Initial stock check
|
|
// expect($product->fresh()->stock)->toBe(20);
|
|
|
|
deleteJson(route('purchase.destroy', $purchase))
|
|
->assertRedirect()
|
|
->assertSessionHas('success');
|
|
|
|
assertSoftDeleted('purchases', ['id' => $purchase->id]);
|
|
|
|
// Stock should be decreased (20 - 10 = 10)
|
|
expect($product->fresh()->stock)->toBe(10);
|
|
});
|
|
|
|
it('can delete purchases in bulk', function () {
|
|
$purchases = Purchase::factory()->count(3)->create();
|
|
$ids = $purchases->pluck('id')->toArray();
|
|
|
|
deleteJson(route('purchase.bulkDestroy'), ['ids' => $ids])
|
|
->assertRedirect()
|
|
->assertSessionHas('success');
|
|
|
|
foreach ($ids as $id) {
|
|
assertSoftDeleted('purchases', ['id' => $id]);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Purchase Module - Unauthorized Actions', function () {
|
|
beforeEach(function () {
|
|
actingAs(createUnauthorizedUser());
|
|
});
|
|
|
|
it('cannot store a purchase without permission', function () {
|
|
postJson(route('purchase.store'), ['purchase_date' => now()->format('Y-m-d')])
|
|
->assertStatus(403);
|
|
});
|
|
|
|
it('cannot update a purchase without permission', function () {
|
|
$purchase = Purchase::factory()->create();
|
|
patchJson(route('purchase.update', $purchase), ['purchase_date' => now()->format('Y-m-d')])
|
|
->assertStatus(403);
|
|
});
|
|
|
|
it('cannot delete a purchase without permission', function () {
|
|
$purchase = Purchase::factory()->create();
|
|
deleteJson(route('purchase.destroy', $purchase))
|
|
->assertStatus(403);
|
|
});
|
|
});
|