user = User::factory()->create([ 'password' => 'password', 'phone_number' => 61929248, ]); // Helper to create product with stock $this->createProductWithStock = function ($stock = 10) { // Since we don't have factories for Channel/Inventory, and the Controller logic // for 'stock' check seems to rely on the 'stock' column on the products table // (based on CartController::index logic: if ($cartItem->product->stock < ...)), // we can just create a product with the stock attribute. // If Product factory doesn't exist, we'll create it manually. return Product::create([ 'name' => 'Test Product', 'slug' => 'test-product-'.uniqid(), 'stock' => $stock, 'price_amount' => 100, 'is_visible' => true, ]); }; }); test('unauthenticated user cannot access cart', function () { $this->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->getJson('/api/v1/carts') ->assertStatus(401); }); test('authenticated user can view empty cart', function () { $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->getJson('/api/v1/carts'); $response->assertStatus(200) ->assertJson(['data' => []]); }); test('authenticated user can store item in cart', function () { $product = ($this->createProductWithStock)(10); $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->postJson('/api/v1/carts', [ 'product_id' => $product->id, 'product_quantity' => 2, ]); $response->assertStatus(201); $this->assertDatabaseHas('cart_items', [ 'user_id' => $this->user->id, 'product_id' => $product->id, 'product_quantity' => 2, ]); }); test('storing item updates quantity if already in cart', function () { $product = ($this->createProductWithStock)(10); // Initial add CartItem::create([ 'user_id' => $this->user->id, 'product_id' => $product->id, 'product_quantity' => 1, ]); // Update quantity $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->postJson('/api/v1/carts', [ 'product_id' => $product->id, 'product_quantity' => 5, ]); $response->assertStatus(201); $this->assertDatabaseHas('cart_items', [ 'user_id' => $this->user->id, 'product_id' => $product->id, 'product_quantity' => 5, // Should update to new quantity, not add ]); }); test('cart validation fails if product does not exist', function () { $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->postJson('/api/v1/carts', [ 'product_id' => 99999, 'product_quantity' => 1, ]); // Expecting validation error. // Note: The custom rule ProductStockIsAvailable might handle existence check or it might fail before that. // The request doesn't use 'exists' rule explicitly but checks stock. $response->assertStatus(422) ->assertJsonValidationErrors(['product_id']); }); test('cart validation fails if quantity is invalid', function () { $product = ($this->createProductWithStock)(10); $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->postJson('/api/v1/carts', [ 'product_id' => $product->id, 'product_quantity' => 0, // Min is 1 ]); $response->assertStatus(422) ->assertJsonValidationErrors(['product_quantity']); }); test('cart validation fails if stock is insufficient', function () { $product = ($this->createProductWithStock)(5); $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->postJson('/api/v1/carts', [ 'product_id' => $product->id, 'product_quantity' => 10, ]); $response->assertStatus(422) ->assertJsonValidationErrors(['product_id']); // Rule attached to product_id }); test('authenticated user can list cart items', function () { $product1 = ($this->createProductWithStock)(10); $product2 = ($this->createProductWithStock)(10); CartItem::create(['user_id' => $this->user->id, 'product_id' => $product1->id, 'product_quantity' => 2]); CartItem::create(['user_id' => $this->user->id, 'product_id' => $product2->id, 'product_quantity' => 1]); $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->getJson('/api/v1/carts'); $response->assertStatus(200) ->assertJsonCount(2, 'data') ->assertJsonStructure([ 'data' => [ '*' => [ 'id', 'user_id', 'product_id', 'product_quantity', 'product', ], ], ]); }); test('index adjusts quantity if stock decreased', function () { $product = ($this->createProductWithStock)(10); CartItem::create(['user_id' => $this->user->id, 'product_id' => $product->id, 'product_quantity' => 10]); // Reduce stock $product->update(['stock' => 5]); $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->getJson('/api/v1/carts'); $response->assertStatus(200); // Check DB $this->assertDatabaseHas('cart_items', [ 'user_id' => $this->user->id, 'product_id' => $product->id, 'product_quantity' => 5, ]); }); test('index removes item if out of stock', function () { $product = ($this->createProductWithStock)(10); CartItem::create(['user_id' => $this->user->id, 'product_id' => $product->id, 'product_quantity' => 5]); // Out of stock $product->update(['stock' => 0]); $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->getJson('/api/v1/carts'); $response->assertStatus(200) ->assertJsonCount(0, 'data'); // Check DB $this->assertDatabaseMissing('cart_items', [ 'user_id' => $this->user->id, 'product_id' => $product->id, ]); }); test('authenticated user can remove specific item from cart', function () { $product = ($this->createProductWithStock)(10); CartItem::create(['user_id' => $this->user->id, 'product_id' => $product->id, 'product_quantity' => 2]); $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->patchJson('/api/v1/carts', [ 'product_id' => $product->id, ]); $response->assertStatus(200); $this->assertDatabaseMissing('cart_items', [ 'user_id' => $this->user->id, 'product_id' => $product->id, ]); }); test('remove item validation fails if product_id is missing', function () { $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->patchJson('/api/v1/carts', []); $response->assertStatus(422) ->assertJsonValidationErrors(['product_id']); }); test('authenticated user can clear entire cart', function () { $product1 = ($this->createProductWithStock)(10); $product2 = ($this->createProductWithStock)(10); CartItem::create(['user_id' => $this->user->id, 'product_id' => $product1->id, 'product_quantity' => 2]); CartItem::create(['user_id' => $this->user->id, 'product_id' => $product2->id, 'product_quantity' => 1]); $response = $this->actingAs($this->user, 'sanctum') ->withHeaders(['Api-Token' => config('ecommerce.api.token')]) ->deleteJson('/api/v1/carts'); $response->assertStatus(200); $this->assertDatabaseCount('cart_items', 0); });