store/tests/Feature/Admin/Manage/RestockTest.php

487 lines
16 KiB
PHP

<?php
use App\Enums\OwnerVerificationAction;
use App\Enums\OwnerVerificationStatus;
use App\Enums\Permission as PermissionEnum;
use App\Enums\ProductStockQuality;
use App\Models\OwnerVerificationRequest;
use App\Models\ProductVariant;
use App\Models\Restock;
use App\Models\RestockItem;
use App\Models\User;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->seed(RolePermissionSeeder::class);
});
function createRestockUserWithPermission(PermissionEnum ...$permissions): User
{
$user = User::factory()->create();
$user->givePermissionTo(
array_merge(
[PermissionEnum::DASHBOARD_VIEW->value],
array_map(fn (PermissionEnum $p) => $p->value, $permissions)
)
);
$user->forgetCachedPermissions();
return $user;
}
function createRestockOwnerUser(PermissionEnum ...$permissions): User
{
$user = User::factory()->create();
$user->givePermissionTo(
array_merge(
[
PermissionEnum::DASHBOARD_VIEW->value,
PermissionEnum::OWNER_VERIFICATIONS_VERIFY->value,
],
array_map(fn (PermissionEnum $p) => $p->value, $permissions)
)
);
$user->forgetCachedPermissions();
return $user;
}
function createRestockVerifierUser(): User
{
$user = User::factory()->create();
$user->givePermissionTo([
PermissionEnum::DASHBOARD_VIEW->value,
PermissionEnum::OWNER_VERIFICATIONS_VIEW->value,
PermissionEnum::OWNER_VERIFICATIONS_VERIFY->value,
PermissionEnum::OWNER_VERIFICATIONS_REJECT->value,
]);
$user->forgetCachedPermissions();
return $user;
}
function approveLatestRestockVerificationRequest(User $verifier): OwnerVerificationRequest
{
$verificationRequest = OwnerVerificationRequest::query()->pending()->latest()->firstOrFail();
test()->actingAs($verifier)
->post(route('admin.manage.owner_verifications.approve_request', $verificationRequest))
->assertRedirect();
return $verificationRequest->fresh();
}
function getRestockItemsPayload(ProductVariant $variant, int $quantity = 2, int $unitPrice = 50000): array
{
return [
[
'product_variant_id' => $variant->id,
'quantity' => $quantity,
'unit_price' => $unitPrice,
],
];
}
describe('Restock CRUD', function () {
test('owner can create restock and stock is incremented immediately', function () {
$user = createRestockOwnerUser(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
);
$variant = ProductVariant::factory()->create(['stock' => 10]);
$initialStock = $variant->stock;
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'notes' => 'Restock test',
'stock_type' => 'good',
'items' => getRestockItemsPayload($variant, 3),
])
->assertRedirect(route('admin.manage.restocks.index'));
$restock = Restock::query()->latest()->first();
expect($restock)->not->toBeNull();
expect($restock->notes)->toBe('Restock test');
expect($restock->stock_type)->toBe(ProductStockQuality::GOOD);
expect($variant->fresh()->stock)->toBe($initialStock + 3);
});
test('owner can create restock with retail stock type', function () {
$user = createRestockOwnerUser(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
);
$variant = ProductVariant::factory()->create(['retail_stock' => 5]);
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'notes' => 'Restock eceran',
'stock_type' => 'retail',
'items' => getRestockItemsPayload($variant, 2),
])
->assertRedirect(route('admin.manage.restocks.index'));
expect($variant->fresh()->retail_stock)->toBe(7);
});
test('owner can create restock with reject stock type', function () {
$user = createRestockOwnerUser(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
);
$variant = ProductVariant::factory()->create(['reject_stock' => 3]);
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'notes' => 'Restock reject',
'stock_type' => 'reject',
'items' => getRestockItemsPayload($variant, 4),
])
->assertRedirect(route('admin.manage.restocks.index'));
expect($variant->fresh()->reject_stock)->toBe(7);
});
test('owner can delete restock and stock is decremented', function () {
$user = createRestockOwnerUser(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
PermissionEnum::RESTOCKS_DELETE,
);
$variant = ProductVariant::factory()->create(['stock' => 10]);
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'stock_type' => 'good',
'items' => getRestockItemsPayload($variant, 3),
])
->assertRedirect();
expect($variant->fresh()->stock)->toBe(13);
$restock = Restock::query()->latest()->first();
$this->actingAs($user)
->delete(route('admin.manage.restocks.destroy', $restock))
->assertRedirect();
expect($variant->fresh()->stock)->toBe(10);
});
test('index page returns ok', function () {
$user = createRestockOwnerUser(PermissionEnum::RESTOCKS_VIEW);
$this->actingAs($user)
->get(route('admin.manage.restocks.index'))
->assertOk();
});
test('create page returns ok', function () {
$user = createRestockOwnerUser(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
);
$this->actingAs($user)
->get(route('admin.manage.restocks.create'))
->assertOk();
});
test('edit page returns ok', function () {
$user = createRestockOwnerUser(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
PermissionEnum::RESTOCKS_UPDATE,
);
$variant = ProductVariant::factory()->create();
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'stock_type' => 'good',
'items' => getRestockItemsPayload($variant),
])
->assertRedirect();
$restock = Restock::query()->latest()->first();
$this->actingAs($user)
->get(route('admin.manage.restocks.edit', $restock))
->assertOk();
});
});
describe('Restock Owner Verification', function () {
test('non-owner create restock submits verification without changing stock', function () {
$user = createRestockUserWithPermission(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
);
$variant = ProductVariant::factory()->create(['stock' => 10]);
$initialStock = $variant->stock;
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'stock_type' => 'good',
'items' => getRestockItemsPayload($variant, 3),
])
->assertRedirect(route('admin.manage.restocks.index'));
$restock = Restock::query()->latest()->first();
expect($restock)->not->toBeNull();
$this->assertDatabaseHas('owner_verification_requests', [
'subject_type' => Restock::class,
'subject_id' => $restock->id,
'action' => OwnerVerificationAction::CREATE->value,
'status' => OwnerVerificationStatus::PENDING->value,
]);
expect($variant->fresh()->stock)->toBe($initialStock);
});
test('approving create restock increments stock', function () {
$user = createRestockUserWithPermission(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
);
$verifier = createRestockVerifierUser();
$variant = ProductVariant::factory()->create(['stock' => 10]);
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'stock_type' => 'good',
'items' => getRestockItemsPayload($variant, 3),
])
->assertRedirect();
approveLatestRestockVerificationRequest($verifier);
expect($variant->fresh()->stock)->toBe(13);
});
test('rejecting create restock does not change stock and deletes restock', function () {
$user = createRestockUserWithPermission(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
);
$verifier = createRestockVerifierUser();
$variant = ProductVariant::factory()->create(['stock' => 10]);
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'stock_type' => 'good',
'items' => getRestockItemsPayload($variant, 3),
])
->assertRedirect();
$restock = Restock::query()->latest()->first();
$restockId = $restock->id;
$verificationRequest = OwnerVerificationRequest::query()->pending()->latest()->firstOrFail();
$this->actingAs($verifier)
->post(route('admin.manage.owner_verifications.reject_request', $verificationRequest))
->assertRedirect();
expect($variant->fresh()->stock)->toBe(10);
expect(Restock::find($restockId))->toBeNull();
expect(Restock::withTrashed()->find($restockId))->not->toBeNull();
});
test('non-owner delete restock submits verification request', function () {
$user = createRestockUserWithPermission(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
PermissionEnum::RESTOCKS_DELETE,
);
$verifier = createRestockVerifierUser();
$variant = ProductVariant::factory()->create(['stock' => 10]);
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'stock_type' => 'good',
'items' => getRestockItemsPayload($variant, 3),
])
->assertRedirect();
$restock = Restock::query()->latest()->first();
approveLatestRestockVerificationRequest($verifier);
expect($variant->fresh()->stock)->toBe(13);
$this->actingAs($user)
->delete(route('admin.manage.restocks.destroy', $restock))
->assertRedirect();
$this->assertDatabaseHas('owner_verification_requests', [
'subject_type' => Restock::class,
'subject_id' => $restock->id,
'action' => OwnerVerificationAction::DELETE->value,
'status' => OwnerVerificationStatus::PENDING->value,
]);
expect($variant->fresh()->stock)->toBe(13);
});
test('approving delete restock decrements stock', function () {
$user = createRestockUserWithPermission(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
PermissionEnum::RESTOCKS_DELETE,
);
$verifier = createRestockVerifierUser();
$variant = ProductVariant::factory()->create(['stock' => 10]);
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'stock_type' => 'good',
'items' => getRestockItemsPayload($variant, 3),
])
->assertRedirect();
$restock = Restock::query()->latest()->first();
approveLatestRestockVerificationRequest($verifier);
expect($variant->fresh()->stock)->toBe(13);
$this->actingAs($user)
->delete(route('admin.manage.restocks.destroy', $restock))
->assertRedirect();
approveLatestRestockVerificationRequest($verifier);
expect($variant->fresh()->stock)->toBe(10);
});
test('non-owner update restock submits verification request', function () {
$user = createRestockUserWithPermission(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
PermissionEnum::RESTOCKS_UPDATE,
);
$verifier = createRestockVerifierUser();
$variant = ProductVariant::factory()->create(['stock' => 10]);
$otherVariant = ProductVariant::factory()->create(['stock' => 5]);
$this->actingAs($user)
->post(route('admin.manage.restocks.store'), [
'stock_type' => 'good',
'items' => getRestockItemsPayload($variant, 2),
])
->assertRedirect();
$restock = Restock::query()->latest()->first();
approveLatestRestockVerificationRequest($verifier);
expect($variant->fresh()->stock)->toBe(12);
$this->actingAs($user)
->put(route('admin.manage.restocks.update', $restock), [
'stock_type' => 'good',
'items' => getRestockItemsPayload($otherVariant, 4),
])
->assertRedirect();
$this->assertDatabaseHas('owner_verification_requests', [
'subject_type' => Restock::class,
'subject_id' => $restock->id,
'action' => OwnerVerificationAction::UPDATE->value,
'status' => OwnerVerificationStatus::PENDING->value,
]);
});
});
describe('Restock Draft Items', function () {
test('draft item can be created and deleted', function () {
$user = createRestockOwnerUser(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
);
$variant = ProductVariant::factory()->create();
$this->actingAs($user)
->postJson(route('admin.manage.restocks.draft_items.store'), [
'product_variant_id' => $variant->id,
'quantity' => 5,
'unit_price' => 50000,
])
->assertOk();
$this->assertDatabaseHas('restock_items', [
'user_id' => $user->id,
'product_variant_id' => $variant->id,
'quantity' => 5,
'unit_price' => 50000,
'restock_id' => null,
]);
$itemId = RestockItem::where('user_id', $user->id)
->where('product_variant_id', $variant->id)
->whereNull('restock_id')
->value('id');
$this->actingAs($user)
->deleteJson(route('admin.manage.restocks.draft_items.destroy', ['productVariant' => $variant->id]))
->assertOk();
$this->assertDatabaseMissing('restock_items', ['id' => $itemId]);
});
test('draft items are cleared when opening create page', function () {
$user = createRestockOwnerUser(
PermissionEnum::RESTOCKS_VIEW,
PermissionEnum::RESTOCKS_CREATE,
);
$variant = ProductVariant::factory()->create();
$this->actingAs($user)
->postJson(route('admin.manage.restocks.draft_items.store'), [
'product_variant_id' => $variant->id,
'quantity' => 5,
'unit_price' => 50000,
])
->assertOk();
$itemId = RestockItem::where('user_id', $user->id)
->where('product_variant_id', $variant->id)
->whereNull('restock_id')
->value('id');
expect($itemId)->not->toBeNull();
$this->actingAs($user)
->get(route('admin.manage.restocks.create'))
->assertOk();
expect(RestockItem::find($itemId))->toBeNull();
});
});