create(); $store = StoreModel::factory()->create(['level_id' => $level->id]); $product = ProductModel::factory()->create([ 'status' => ProductModel::STATUS_ON, 'cost_price' => $price, ]); return [$store, $product, UserModel::factory()->forStore($store->id)->create()]; } /** 加购成功:数量入库、返回购物车项 */ public function test_add_to_cart_succeeds(): void { [, $product, $user] = $this->makeStoreWithProduct('5.50'); $this->actingAsMiniUser($user); $this->postJson('/mini/cart', [ 'product_id' => $product->id, 'quantity' => 2.5, ])->assertOk() ->assertJsonPath('success', true) ->assertJsonPath('data.quantity', '2.50') ->assertJsonStructure(['data' => ['id', 'quantity']]); $cart = CartModel::first(); $this->assertNotNull($cart); $this->assertSame($user->id, $cart->user_id); $this->assertSame('2.50', (string) $cart->quantity); } /** 重复加购同一商品合并累加(同商品唯一行) */ public function test_duplicate_add_merges_quantity(): void { [, $product, $user] = $this->makeStoreWithProduct(); $this->actingAsMiniUser($user); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 2]); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 3]) ->assertJsonPath('data.quantity', '5.00'); $this->assertSame(1, CartModel::count()); $this->assertSame('5.00', (string) CartModel::first()->quantity); } /** 已下架商品拒绝加购 */ public function test_off_shelf_product_rejected(): void { [, $product, $user] = $this->makeStoreWithProduct(); $product->update(['status' => ProductModel::STATUS_OFF]); $this->actingAsMiniUser($user); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]) ->assertOk() ->assertJsonPath('success', false); $this->assertSame(0, CartModel::count()); } /** 已软删除商品拒绝加购 */ public function test_soft_deleted_product_rejected(): void { [, $product, $user] = $this->makeStoreWithProduct(); $product->delete(); $this->actingAsMiniUser($user); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]) ->assertJsonPath('success', false); $this->assertSame(0, CartModel::count()); } /** 门店绑定的客户等级被删除时拒绝加购 */ public function test_store_level_missing_rejected(): void { [$store, $product, $user] = $this->makeStoreWithProduct(); CustomerLevelModel::whereKey($store->level_id)->delete(); $this->actingAsMiniUser($user); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]) ->assertJsonPath('success', false) ->assertJsonPath('msg', '门店未设置客户等级,无法加购,请联系客服'); $this->assertSame(0, CartModel::count()); } /** 未绑定门店的用户拒绝加购 */ public function test_unbound_user_rejected(): void { [, $product] = $this->makeStoreWithProduct(); $this->actingAsMiniUser(UserModel::factory()->create()); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]) ->assertJsonPath('success', false) ->assertJsonPath('msg', '尚未绑定门店,请联系客服处理'); } /** 门店未设置客户等级时拒绝加购 */ public function test_store_without_level_rejected(): void { [, $product] = $this->makeStoreWithProduct(); $store = StoreModel::factory()->create(['level_id' => 0]); $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]) ->assertJsonPath('success', false); } /** 列表:实时等级上浮价、服务端金额、汇总(同店两商品,新加入在前) */ public function test_list_with_prices_amounts_and_totals(): void { $level = CustomerLevelModel::factory()->create(); $store = StoreModel::factory()->create(['level_id' => $level->id]); $p1 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.50']); $p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '3.00']); $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); $this->postJson('/mini/cart', ['product_id' => $p1->id, 'quantity' => 3]); $this->postJson('/mini/cart', ['product_id' => $p2->id, 'quantity' => 2]); $this->getJson('/mini/cart')->assertOk() ->assertJsonPath('success', true) // items 按 id 倒序:后加入的 p2 在前 ->assertJsonPath('data.items.0.product_id', $p2->id) ->assertJsonPath('data.items.0.price', '3.00') ->assertJsonPath('data.items.0.quantity', '2.00') ->assertJsonPath('data.items.0.amount', '6.00') ->assertJsonPath('data.items.0.status', 1) ->assertJsonPath('data.items.1.product_id', $p1->id) ->assertJsonPath('data.items.1.price', '5.50') ->assertJsonPath('data.items.1.amount', '16.50') ->assertJsonPath('data.items.1.status', 1) ->assertJsonPath('data.total_count', 2) ->assertJsonPath('data.total_quantity', '5.00') ->assertJsonPath('data.total_amount', '22.50'); } /** 列表:下架商品标记不可购,不计入汇总 */ public function test_list_marks_off_shelf_item_unbuyable(): void { $level = CustomerLevelModel::factory()->create(); $store = StoreModel::factory()->create(['level_id' => $level->id]); $p1 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.50']); $p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '3.00']); $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); $this->postJson('/mini/cart', ['product_id' => $p1->id, 'quantity' => 3]); $this->postJson('/mini/cart', ['product_id' => $p2->id, 'quantity' => 2]); $p1->update(['status' => ProductModel::STATUS_OFF]); $this->getJson('/mini/cart')->assertOk() ->assertJsonPath('data.items.1.status', 0) ->assertJsonPath('data.items.1.amount', null) ->assertJsonPath('data.total_count', 2) ->assertJsonPath('data.total_quantity', '2.00') ->assertJsonPath('data.total_amount', '6.00'); } /** 空购物车返回空列表 */ public function test_empty_cart_returns_empty_items(): void { $store = StoreModel::factory()->create(['level_id' => CustomerLevelModel::factory()->create()->id]); $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); $this->getJson('/mini/cart')->assertOk() ->assertJsonPath('data.items', []) ->assertJsonPath('data.total_count', 0) ->assertJsonPath('data.total_amount', '0.00'); } /** 修改数量 */ public function test_update_quantity(): void { [, $product, $user] = $this->makeStoreWithProduct(); $this->actingAsMiniUser($user); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]); $cart = CartModel::first(); $this->putJson("/mini/cart/{$cart->id}", ['quantity' => 7]) ->assertJsonPath('success', true) ->assertJsonPath('data.quantity', '7.00'); $this->assertSame('7.00', (string) $cart->fresh()->quantity); } /** 修改他人购物车项拒绝(同店另一用户) */ public function test_update_other_users_item_rejected(): void { [$store, $product, $userA] = $this->makeStoreWithProduct(); $this->actingAsMiniUser($userA); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]); $cart = CartModel::first(); $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); $this->putJson("/mini/cart/{$cart->id}", ['quantity' => 9]) ->assertJsonPath('success', false) ->assertJsonPath('msg', '购物车项不存在'); $this->assertSame('1.00', (string) $cart->fresh()->quantity, '他人改数量不应生效'); } /** 修改不存在的购物车项拒绝 */ public function test_update_missing_item_rejected(): void { [$store, $product, $user] = $this->makeStoreWithProduct(); $this->actingAsMiniUser($user); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]); $this->putJson('/mini/cart/999999', ['quantity' => 1])->assertJsonPath('success', false); } /** 数量校验:0 与负值拒绝 */ public function test_invalid_quantity_rejected(): void { [, $product, $user] = $this->makeStoreWithProduct(); $this->actingAsMiniUser($user); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 0]) ->assertOk() ->assertJsonPath('success', false) ->assertJsonPath('msg', '订货数量必须大于 0'); $this->assertSame(0, CartModel::count()); } /** 删除单项 */ public function test_delete_item(): void { [, $product, $user] = $this->makeStoreWithProduct(); $this->actingAsMiniUser($user); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]); $cart = CartModel::first(); $this->deleteJson("/mini/cart/{$cart->id}")->assertJsonPath('success', true); $this->assertSame(0, CartModel::count()); } /** 删除他人购物车项拒绝 */ public function test_delete_other_users_item_rejected(): void { [$store, $product, $userA] = $this->makeStoreWithProduct(); $this->actingAsMiniUser($userA); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]); $cart = CartModel::first(); $this->actingAsMiniUser(UserModel::factory()->forStore($store->id)->create()); $this->deleteJson("/mini/cart/{$cart->id}") ->assertJsonPath('success', false) ->assertJsonPath('msg', '购物车项不存在'); $this->assertSame(1, CartModel::count()); } /** 清空购物车仅影响当前用户 */ public function test_clear_only_own_cart(): void { $level = CustomerLevelModel::factory()->create(); $store = StoreModel::factory()->create(['level_id' => $level->id]); $p1 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']); $p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']); $userA = UserModel::factory()->forStore($store->id)->create(); $userB = UserModel::factory()->forStore($store->id)->create(); $this->actingAsMiniUser($userA); $this->postJson('/mini/cart', ['product_id' => $p1->id, 'quantity' => 1]); $this->postJson('/mini/cart', ['product_id' => $p2->id, 'quantity' => 1]); $this->actingAsMiniUser($userB); $this->postJson('/mini/cart', ['product_id' => $p1->id, 'quantity' => 1]); $this->actingAsMiniUser($userA); $this->deleteJson('/mini/cart')->assertJsonPath('success', true); $this->assertSame(0, CartModel::where('user_id', $userA->id)->count()); $this->assertSame(1, CartModel::where('user_id', $userB->id)->count(), '他人购物车不受影响'); } /** 未登录访问购物车 → 401 */ public function test_unauthenticated_returns_401(): void { $this->getJson('/mini/cart')->assertStatus(401); } /** 跨用户列表隔离:B 看不到 A 的购物车 */ public function test_cart_isolated_between_users(): void { $level = CustomerLevelModel::factory()->create(); $store = StoreModel::factory()->create(['level_id' => $level->id]); $product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '5.00']); $userA = UserModel::factory()->forStore($store->id)->create(); $userB = UserModel::factory()->forStore($store->id)->create(); $this->actingAsMiniUser($userA); $this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 1]); $this->actingAsMiniUser($userB); $this->getJson('/mini/cart')->assertOk() ->assertJsonPath('data.items', []) ->assertJsonPath('data.total_count', 0); } }