'11111111-1111-1111-1111-111111111111', 'name' => 'Admin BerasPro', 'email' => 'admin@beraspro.test', 'role' => 'admin', ]; protected function setUp(): void { parent::setUp(); InsForgeFake::reset(); InsForgeFake::fakeRest(); } private function actingAsRole(string $role): void { $user = new User; $user->forceFill([ 'id' => "33333333-3333-3333-3333-{$role}0000000", 'name' => ucfirst($role), 'email' => "{$role}@beraspro.test", 'role' => $role, 'is_active' => true, ]); $this->actingAs($user); } public function test_restock_creates_movement_and_updates_balance(): void { $this->actingAsRole('admin'); InsForgeFake::seed('stocks', [ 'id' => 'cccc3333-0000-0000-0000-000000000001', 'product_id' => 'p-1', 'quantity' => 100, ]); app(StockService::class)->restock('p-1', 250, 'gudang A', auth()->user()); $this->assertSame(350, InsForgeFake::$store['stocks']['cccc3333-0000-0000-0000-000000000001']['quantity']); $movement = end(InsForgeFake::$store['stock_movements']); $this->assertSame('restock', $movement['type']); $this->assertSame(250, $movement['quantity']); $this->assertSame('p-1', $movement['product_id']); } public function test_adjust_sets_absolute_balance_with_signed_delta(): void { $this->actingAsRole('admin'); InsForgeFake::seed('stocks', [ 'id' => 'cccc3333-0000-0000-0000-000000000002', 'product_id' => 'p-2', 'quantity' => 500, ]); app(StockService::class)->adjust('p-2', 420, 'selisih fisik'); $this->assertSame(420, InsForgeFake::$store['stocks']['cccc3333-0000-0000-0000-000000000002']['quantity']); $movement = end(InsForgeFake::$store['stock_movements']); $this->assertSame('adjustment', $movement['type']); $this->assertSame(-80, $movement['quantity']); } public function test_adjust_creates_stock_row_when_missing(): void { $this->actingAsRole('admin'); app(StockService::class)->adjust('p-new', 25, 'stok awal'); $row = array_values(array_filter( InsForgeFake::$store['stocks'] ?? [], fn (array $row): bool => $row['product_id'] === 'p-new', )); $this->assertCount(1, $row); $this->assertSame(25, reset($row)['quantity']); } public function test_activity_log_written_for_mutation(): void { $this->actingAsRole('admin'); InsForgeFake::seed('products', [ 'id' => 'p-log', 'code' => 'BERAS-009', 'name' => 'Beras Log', ]); InsForgeFake::seed('stocks', [ 'id' => 'cccc3333-0000-0000-0000-000000000003', 'product_id' => 'p-log', 'quantity' => 10, ]); app(StockService::class)->restock('p-log', 5, null, auth()->user()); $logs = array_values(InsForgeFake::$store['activity_logs'] ?? []); $log = end($logs); $this->assertNotFalse($log); $this->assertSame('stock_restocked', $log['action']); $this->assertSame('p-log', $log['entity_id']); } public function test_stocks_page_lists_balances_for_operator(): void { $this->actingAsRole('operator'); InsForgeFake::seed('stocks', [ 'id' => 'cccc3333-0000-0000-0000-000000000004', 'product_id' => 'p-view', 'quantity' => 9, 'updated_at' => '2026-08-25T02:00:00+00:00', 'products' => ['code' => 'BERAS-010', 'name' => 'Beras Kritis', 'stock_minimum' => 50, 'is_active' => true], ]); Livewire::test(Stocks::class) ->assertSuccessful() ->assertSee('Beras Kritis') ->assertSee('kritis'); } public function test_movements_page_filters_by_type(): void { $this->actingAsRole('operator'); Http::fake([ '*/api/auth/refresh' => Http::response(['accessToken' => 'x.y.z'], 200), '*/api/database/records/stock_movements*' => fn (Request $request) => InsForgeFake::handleRecordsRequest($request), ]); InsForgeFake::seed('stock_movements', [ 'id' => 'dddd4444-0000-0000-0000-000000000001', 'product_id' => 'p-a', 'type' => 'restock', 'quantity' => 100, 'notes' => 'awal', ]); InsForgeFake::seed('stock_movements', [ 'id' => 'dddd4444-0000-0000-0000-000000000002', 'product_id' => 'p-b', 'type' => 'adjustment', 'quantity' => -3, 'notes' => 'rusak', ]); Livewire::test(StockMovements::class) ->filterTable('type', ['value' => 'adjustment']) ->assertSuccessful() ->assertSee('rusak'); } }