$this->storefrontKey, ]; } /** * Get headers with storefront key + customer auth token */ protected function authHeaders(Customer $customer): array { $token = $customer->createToken('test-token')->plainTextToken; return [ 'Authorization' => "Bearer {$token}", 'X-STOREFRONT-KEY' => $this->storefrontKey, ]; } /** * Seed required database records (channel, customer group, category) */ protected function seedRequiredData(): void { try { if (! \Webkul\Category\Models\Category::exists()) { \Webkul\Category\Models\Category::factory()->create([ 'parent_id' => null, ]); } if (! Channel::exists()) { Channel::factory()->create(); } if (! CustomerGroup::where('code', 'general')->exists()) { CustomerGroup::create([ 'code' => 'general', 'name' => 'General', 'is_user_defined' => 0, ]); } } catch (\Exception $e) { $this->markTestSkipped('Test database not properly configured: '.$e->getMessage()); } } /** * Create a customer and return it */ protected function createCustomer(array $attributes = []): Customer { $this->seedRequiredData(); return Customer::factory()->create($attributes); } /** * Create a test customer with a valid token for Bearer authentication * Returns an array with customer and token keys */ protected function createTestCustomer(): array { // Create customer with a token field (same way it's created during registration) $customer = $this->createCustomer([ 'token' => md5(uniqid(rand(), true)), ]); return [ 'customer' => $customer, 'token' => $customer->token, ]; } protected function getAttributeIdByCode(string $code): int { $attributeId = Attribute::query()->where('code', $code)->value('id'); if (! $attributeId) { $this->markTestSkipped(sprintf('Required attribute "%s" not found. Run Bagisto seeders for attributes.', $code)); } return (int) $attributeId; } protected function upsertProductAttributeValue( int $productId, string $attributeCode, mixed $value, ?string $locale = 'en', ?string $channel = 'default' ): void { $attribute = Attribute::query()->where('code', $attributeCode)->first(); if (! $attribute) { $this->markTestSkipped(sprintf('Required attribute "%s" not found. Run Bagisto seeders for attributes.', $attributeCode)); } $type = (string) ($attribute->type ?? 'text'); $field = ProductAttributeValue::$attributeTypeFields[$type] ?? 'text_value'; $payload = [ 'product_id' => $productId, 'attribute_id' => (int) $attribute->id, 'locale' => $locale, 'channel' => $channel, 'text_value' => null, 'boolean_value'=> null, 'integer_value'=> null, 'float_value' => null, 'datetime_value'=> null, 'date_value' => null, 'json_value' => null, ]; $normalized = $value; if ($field === 'boolean_value') { $normalized = (bool) $value; } elseif ($field === 'integer_value') { $normalized = (int) $value; } elseif ($field === 'float_value') { $normalized = (float) $value; } elseif ($field === 'json_value') { $normalized = is_string($value) ? $value : json_encode($value); } else { $normalized = is_string($value) ? $value : (string) $value; } $payload[$field] = $normalized; ProductAttributeValue::query()->updateOrCreate( [ 'product_id' => $productId, 'attribute_id' => (int) $attribute->id, 'locale' => $locale, 'channel' => $channel, ], $payload ); } protected function ensureProductIsSaleable(Product $product, ?float $price = 10.0): void { $this->upsertProductAttributeValue($product->id, 'name', 'Test '.$product->sku, 'en', 'default'); $this->upsertProductAttributeValue($product->id, 'url_key', strtolower($product->sku), 'en', 'default'); $this->upsertProductAttributeValue($product->id, 'status', 1, null, 'default'); if ($price !== null) { $this->upsertProductAttributeValue($product->id, 'price', $price, null, 'default'); } } protected function ensureInventory(Product $product, int $qty = 50): void { $inventorySourceId = (int) (DB::table('inventory_sources')->value('id') ?? 0); if (! $inventorySourceId) { $this->markTestSkipped('No inventory_sources found. Run Bagisto seeders for inventory sources.'); } DB::table('product_inventories')->updateOrInsert( [ 'product_id' => $product->id, 'inventory_source_id' => $inventorySourceId, 'vendor_id' => 0, ], [ 'qty' => $qty, ] ); } protected function createBaseProduct(string $type, array $overrides = []): Product { $this->seedRequiredData(); $attributeFamilyId = (int) (DB::table('attribute_families')->value('id') ?? 1); $product = Product::factory()->create([ 'type' => $type, 'attribute_family_id' => $attributeFamilyId, ...$overrides, ]); $this->ensureProductIsSaleable($product, 10.0); return $product; } protected function createAttributeOption(int $attributeId, string $label, string $locale = 'en'): int { /** @var AttributeOption $option */ $option = AttributeOption::query()->create([ 'attribute_id' => $attributeId, 'admin_name' => $label, 'sort_order' => 1, ]); DB::table('attribute_option_translations')->insert([ 'attribute_option_id' => $option->id, 'locale' => $locale, 'label' => $label, ]); return (int) $option->id; } /** * Get an existing product with inventory from the database for testing * Returns an array with product and inventory_source_id keys */ protected function createTestProduct(): array { // Find an existing product that has inventory in the database $productWithInventory = DB::table('product_inventories') ->select('product_id') ->groupBy('product_id') ->havingRaw('SUM(qty) > 0') ->first(); if (!$productWithInventory) { throw new \Exception('No products with inventory found in database'); } $productId = $productWithInventory->product_id; // Get the product model $product = Product::find($productId); if (!$product) { throw new \Exception('Product not found with ID: ' . $productId); } return [ 'product' => $product, 'inventory_source_id' => 1, ]; } }