BagistoApiTestCase.php 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <?php
  2. namespace Webkul\BagistoApi\Tests;
  3. use Illuminate\Support\Facades\DB;
  4. use Illuminate\Foundation\Testing\DatabaseTransactions;
  5. use Illuminate\Testing\TestResponse;
  6. use Webkul\Attribute\Models\Attribute;
  7. use Webkul\Attribute\Models\AttributeOption;
  8. use Webkul\BagistoApi\Tests\BagistoApiTest;
  9. use Webkul\Core\Models\Channel;
  10. use Webkul\Customer\Models\Customer;
  11. use Webkul\Customer\Models\CustomerGroup;
  12. use Webkul\Product\Models\Product;
  13. use Webkul\Product\Models\ProductAttributeValue;
  14. /**
  15. * Base test case for all BagistoApi tests.
  16. *
  17. * Provides shared storefront key handling, customer authentication,
  18. * database seeding, and foreign key constraint management.
  19. */
  20. abstract class BagistoApiTestCase extends BagistoApiTest
  21. {
  22. use DatabaseTransactions;
  23. /** Default storefront API key for tests */
  24. protected string $storefrontKey = 'pk_storefront_WaZh0x0FlbKF1suYmDD37YTfkRKm6BJ1';
  25. /** Disable API logging middleware for tests */
  26. protected $withoutMiddleware = [
  27. \Webkul\BagistoApi\Http\Middleware\LogApiRequests::class,
  28. ];
  29. public function setUp(): void
  30. {
  31. parent::setUp();
  32. DB::statement('SET FOREIGN_KEY_CHECKS=0');
  33. }
  34. public function tearDown(): void
  35. {
  36. DB::statement('SET FOREIGN_KEY_CHECKS=1');
  37. parent::tearDown();
  38. }
  39. /**
  40. * Get storefront key header (public API access)
  41. */
  42. protected function storefrontHeaders(): array
  43. {
  44. return [
  45. 'X-STOREFRONT-KEY' => $this->storefrontKey,
  46. ];
  47. }
  48. /**
  49. * Get headers with storefront key + customer auth token
  50. */
  51. protected function authHeaders(Customer $customer): array
  52. {
  53. $token = $customer->createToken('test-token')->plainTextToken;
  54. return [
  55. 'Authorization' => "Bearer {$token}",
  56. 'X-STOREFRONT-KEY' => $this->storefrontKey,
  57. ];
  58. }
  59. /**
  60. * Seed required database records (channel, customer group, category)
  61. */
  62. protected function seedRequiredData(): void
  63. {
  64. try {
  65. if (! \Webkul\Category\Models\Category::exists()) {
  66. \Webkul\Category\Models\Category::factory()->create([
  67. 'parent_id' => null,
  68. ]);
  69. }
  70. if (! Channel::exists()) {
  71. Channel::factory()->create();
  72. }
  73. if (! CustomerGroup::where('code', 'general')->exists()) {
  74. CustomerGroup::create([
  75. 'code' => 'general',
  76. 'name' => 'General',
  77. 'is_user_defined' => 0,
  78. ]);
  79. }
  80. } catch (\Exception $e) {
  81. $this->markTestSkipped('Test database not properly configured: '.$e->getMessage());
  82. }
  83. }
  84. /**
  85. * Create a customer and return it
  86. */
  87. protected function createCustomer(array $attributes = []): Customer
  88. {
  89. $this->seedRequiredData();
  90. return Customer::factory()->create($attributes);
  91. }
  92. /**
  93. * Create a test customer with a valid token for Bearer authentication
  94. * Returns an array with customer and token keys
  95. */
  96. protected function createTestCustomer(): array
  97. {
  98. // Create customer with a token field (same way it's created during registration)
  99. $customer = $this->createCustomer([
  100. 'token' => md5(uniqid(rand(), true)),
  101. ]);
  102. return [
  103. 'customer' => $customer,
  104. 'token' => $customer->token,
  105. ];
  106. }
  107. protected function getAttributeIdByCode(string $code): int
  108. {
  109. $attributeId = Attribute::query()->where('code', $code)->value('id');
  110. if (! $attributeId) {
  111. $this->markTestSkipped(sprintf('Required attribute "%s" not found. Run Bagisto seeders for attributes.', $code));
  112. }
  113. return (int) $attributeId;
  114. }
  115. protected function upsertProductAttributeValue(
  116. int $productId,
  117. string $attributeCode,
  118. mixed $value,
  119. ?string $locale = 'en',
  120. ?string $channel = 'default'
  121. ): void {
  122. $attribute = Attribute::query()->where('code', $attributeCode)->first();
  123. if (! $attribute) {
  124. $this->markTestSkipped(sprintf('Required attribute "%s" not found. Run Bagisto seeders for attributes.', $attributeCode));
  125. }
  126. $type = (string) ($attribute->type ?? 'text');
  127. $field = ProductAttributeValue::$attributeTypeFields[$type] ?? 'text_value';
  128. $payload = [
  129. 'product_id' => $productId,
  130. 'attribute_id' => (int) $attribute->id,
  131. 'locale' => $locale,
  132. 'channel' => $channel,
  133. 'text_value' => null,
  134. 'boolean_value'=> null,
  135. 'integer_value'=> null,
  136. 'float_value' => null,
  137. 'datetime_value'=> null,
  138. 'date_value' => null,
  139. 'json_value' => null,
  140. ];
  141. $normalized = $value;
  142. if ($field === 'boolean_value') {
  143. $normalized = (bool) $value;
  144. } elseif ($field === 'integer_value') {
  145. $normalized = (int) $value;
  146. } elseif ($field === 'float_value') {
  147. $normalized = (float) $value;
  148. } elseif ($field === 'json_value') {
  149. $normalized = is_string($value) ? $value : json_encode($value);
  150. } else {
  151. $normalized = is_string($value) ? $value : (string) $value;
  152. }
  153. $payload[$field] = $normalized;
  154. ProductAttributeValue::query()->updateOrCreate(
  155. [
  156. 'product_id' => $productId,
  157. 'attribute_id' => (int) $attribute->id,
  158. 'locale' => $locale,
  159. 'channel' => $channel,
  160. ],
  161. $payload
  162. );
  163. }
  164. protected function ensureProductIsSaleable(Product $product, ?float $price = 10.0): void
  165. {
  166. $this->upsertProductAttributeValue($product->id, 'name', 'Test '.$product->sku, 'en', 'default');
  167. $this->upsertProductAttributeValue($product->id, 'url_key', strtolower($product->sku), 'en', 'default');
  168. $this->upsertProductAttributeValue($product->id, 'status', 1, null, 'default');
  169. if ($price !== null) {
  170. $this->upsertProductAttributeValue($product->id, 'price', $price, null, 'default');
  171. }
  172. }
  173. protected function ensureInventory(Product $product, int $qty = 50): void
  174. {
  175. $inventorySourceId = (int) (DB::table('inventory_sources')->value('id') ?? 0);
  176. if (! $inventorySourceId) {
  177. $this->markTestSkipped('No inventory_sources found. Run Bagisto seeders for inventory sources.');
  178. }
  179. DB::table('product_inventories')->updateOrInsert(
  180. [
  181. 'product_id' => $product->id,
  182. 'inventory_source_id' => $inventorySourceId,
  183. 'vendor_id' => 0,
  184. ],
  185. [
  186. 'qty' => $qty,
  187. ]
  188. );
  189. }
  190. protected function createBaseProduct(string $type, array $overrides = []): Product
  191. {
  192. $this->seedRequiredData();
  193. $attributeFamilyId = (int) (DB::table('attribute_families')->value('id') ?? 1);
  194. $product = Product::factory()->create([
  195. 'type' => $type,
  196. 'attribute_family_id' => $attributeFamilyId,
  197. ...$overrides,
  198. ]);
  199. $this->ensureProductIsSaleable($product, 10.0);
  200. return $product;
  201. }
  202. protected function createAttributeOption(int $attributeId, string $label, string $locale = 'en'): int
  203. {
  204. /** @var AttributeOption $option */
  205. $option = AttributeOption::query()->create([
  206. 'attribute_id' => $attributeId,
  207. 'admin_name' => $label,
  208. 'sort_order' => 1,
  209. ]);
  210. DB::table('attribute_option_translations')->insert([
  211. 'attribute_option_id' => $option->id,
  212. 'locale' => $locale,
  213. 'label' => $label,
  214. ]);
  215. return (int) $option->id;
  216. }
  217. /**
  218. * Get an existing product with inventory from the database for testing
  219. * Returns an array with product and inventory_source_id keys
  220. */
  221. protected function createTestProduct(): array
  222. {
  223. // Find an existing product that has inventory in the database
  224. $productWithInventory = DB::table('product_inventories')
  225. ->select('product_id')
  226. ->groupBy('product_id')
  227. ->havingRaw('SUM(qty) > 0')
  228. ->first();
  229. if (!$productWithInventory) {
  230. throw new \Exception('No products with inventory found in database');
  231. }
  232. $productId = $productWithInventory->product_id;
  233. // Get the product model
  234. $product = Product::find($productId);
  235. if (!$product) {
  236. throw new \Exception('Product not found with ID: ' . $productId);
  237. }
  238. return [
  239. 'product' => $product,
  240. 'inventory_source_id' => 1,
  241. ];
  242. }
  243. }