购物车
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\CartModel;
|
||||
use App\Models\CustomerLevelModel;
|
||||
use App\Models\ProductModel;
|
||||
use App\Models\ProductPriceModel;
|
||||
use App\Models\StoreModel;
|
||||
use App\Models\UserModel;
|
||||
|
||||
/**
|
||||
* 小程序购物车:加购合并、等级价/金额服务端计算、归属校验、数据隔离、清空
|
||||
*/
|
||||
class CartTest extends ProcurementTestCase
|
||||
{
|
||||
/**
|
||||
* 造一家门店 + 一个上架商品(含等级价)+ 该店用户
|
||||
*
|
||||
* @return array{0: StoreModel, 1: ProductModel, 2: UserModel}
|
||||
*/
|
||||
private function makeStoreWithProduct(string $price = '5.00'): array
|
||||
{
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
$store = StoreModel::factory()->create(['level_id' => $level->id]);
|
||||
$product = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
|
||||
ProductPriceModel::factory()->create([
|
||||
'product_id' => $product->id,
|
||||
'level_id' => $level->id,
|
||||
'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_product_without_level_price_rejected(): void
|
||||
{
|
||||
[, $product, $user] = $this->makeStoreWithProduct();
|
||||
ProductPriceModel::where('product_id', $product->id)->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_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]);
|
||||
$p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
|
||||
ProductPriceModel::factory()->create(['product_id' => $p1->id, 'level_id' => $level->id, 'price' => '5.50']);
|
||||
ProductPriceModel::factory()->create(['product_id' => $p2->id, 'level_id' => $level->id, '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]);
|
||||
$p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
|
||||
ProductPriceModel::factory()->create(['product_id' => $p1->id, 'level_id' => $level->id, 'price' => '5.50']);
|
||||
ProductPriceModel::factory()->create(['product_id' => $p2->id, 'level_id' => $level->id, '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]);
|
||||
$p2 = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
|
||||
ProductPriceModel::factory()->create(['product_id' => $p1->id, 'level_id' => $level->id, 'price' => '5.00']);
|
||||
ProductPriceModel::factory()->create(['product_id' => $p2->id, 'level_id' => $level->id, '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]);
|
||||
ProductPriceModel::factory()->create(['product_id' => $product->id, 'level_id' => $level->id, '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);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ namespace Tests\Feature;
|
||||
use App\Models\StoreModel;
|
||||
use App\Models\UserModel;
|
||||
use App\Services\WechatService;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Modules\SystemTool\Services\SysSiteConfigService;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\HttpClient\Response\MockResponse;
|
||||
|
||||
@@ -17,8 +19,29 @@ class MiniAuthTest extends ProcurementTestCase
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
// 测试环境注入微信配置(生产由 WECHAT_MINI_APPID/SECRET 提供)
|
||||
config(['services.wechat.mini' => ['appid' => 'test_appid', 'secret' => 'test_secret']]);
|
||||
// 微信配置为 DB 驱动(后台「小程序设置」→ sys_site_config,WechatService 经
|
||||
// site_config('wechatMini') 读取):测试落库并刷新配置缓存
|
||||
DB::table('sys_site_config_group')->insert([
|
||||
'id' => 100,
|
||||
'title' => '小程序设置',
|
||||
'key' => 'wechatMini',
|
||||
'remark' => '',
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
DB::table('sys_site_config_items')->insert([
|
||||
[
|
||||
'group_id' => 100, 'key' => 'appid', 'title' => 'APPID', 'describe' => '',
|
||||
'values' => 'test_appid', 'type' => 'Input', 'options' => null, 'props' => null, 'sort' => 0,
|
||||
'created_at' => now(), 'updated_at' => now(),
|
||||
],
|
||||
[
|
||||
'group_id' => 100, 'key' => 'secret', 'title' => 'SecretKey', 'describe' => '',
|
||||
'values' => 'test_secret', 'type' => 'Input', 'options' => null, 'props' => null, 'sort' => 1,
|
||||
'created_at' => now(), 'updated_at' => now(),
|
||||
],
|
||||
]);
|
||||
SysSiteConfigService::refreshSiteConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user