Files
xin-procurement/tests/Feature/MiniProductTest.php
T
2026-09-05 19:40:52 +08:00

211 lines
9.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace Tests\Feature;
use App\Models\CartModel;
use App\Models\CustomerLevelModel;
use App\Models\ProductCategoryModel;
use App\Models\ProductModel;
use App\Models\StoreModel;
/**
* 小程序商品:商品详情接口(免登录浏览,登录门店按等级上浮比例显示换算价);
* 商品列表回归:登录门店场景曾数组误用对象访问 + 价格缺失导致 500
* (门店即用户:登录主体为 StoreModel
*/
class MiniProductTest extends ProcurementTestCase
{
/**
* 造门店 + 上架商品(成本价即售价,等级上浮 0%)
*
* @return array{0: StoreModel, 1: ProductModel, 2: CustomerLevelModel}
*/
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,
'cost_price' => $price,
]);
return [$store, $product, $level];
}
/** 商品详情:未登录浏览 price=null,基础字段完整 */
public function test_product_detail_guest_sees_null_price(): void
{
[, $product] = $this->makeStoreWithProduct();
$data = $this->getJson("/mini/product/{$product->id}")->assertOk()->json('data');
$this->assertSame($product->name, $data['name']);
$this->assertSame($product->spec, $data['spec']);
$this->assertSame($product->unit, $data['unit']);
$this->assertNull($data['price'], '未登录不显示价格');
$this->assertArrayNotHasKey('cost_price', $data, '成本价不得输出到小程序端');
}
/** 商品详情:登录门店按等级显示固定价 */
public function test_product_detail_store_sees_level_price(): void
{
[$store, $product] = $this->makeStoreWithProduct('6.50');
$this->actingAsMiniStore($store);
$data = $this->getJson("/mini/product/{$product->id}")->assertOk()->json('data');
$this->assertSame('6.50', $data['price']);
}
/** 商品详情:按门店等级上浮比例换算(成本价上浮 30%) */
public function test_product_detail_percent_price_calculated_from_cost(): void
{
[$store, $product, $level] = $this->makeStoreWithProduct();
$level->update(['percent' => 30]);
$product->update(['cost_price' => '20.00']);
$this->actingAsMiniStore($store);
$data = $this->getJson("/mini/product/{$product->id}")->assertOk()->json('data');
$this->assertSame('26.00', $data['price'], '20 元上浮 30% = 26.00');
}
/** 商品详情:下架/不存在拒绝 */
public function test_product_detail_off_shelf_or_missing_rejected(): void
{
[, $product] = $this->makeStoreWithProduct();
$product->update(['status' => ProductModel::STATUS_OFF]);
$this->getJson("/mini/product/{$product->id}")->assertJsonPath('success', false);
$this->getJson('/mini/product/99999')->assertJsonPath('success', false);
}
/** 商品列表:登录门店显示等级价(回归:曾数组误用对象访问导致 500) */
public function test_product_list_store_sees_level_price(): void
{
[$store, $product] = $this->makeStoreWithProduct('7.00');
$this->actingAsMiniStore($store);
$rows = $this->getJson('/mini/product/list')->assertOk()->json('data.data');
$row = collect($rows)->firstWhere('id', $product->id);
$this->assertNotNull($row);
$this->assertSame('7.00', $row['price']);
$this->assertArrayNotHasKey('cost_price', $row, '成本价不得输出到小程序端');
}
/** 商品列表:未登录 price=null */
public function test_product_list_guest_sees_null_price(): void
{
[, $product] = $this->makeStoreWithProduct();
$rows = $this->getJson('/mini/product/list')->assertOk()->json('data.data');
$row = collect($rows)->firstWhere('id', $product->id);
$this->assertNotNull($row);
$this->assertNull($row['price']);
}
/** 商品列表:先按分类树展示顺序(深度优先),同分类内按商品 sort 升序、id 升序,未入树分类排最后 */
public function test_product_list_ordered_by_category_tree(): void
{
$rootA = ProductCategoryModel::create(['name' => '蔬菜', 'parent_id' => 0, 'sort' => 0, 'status' => 1]);
$rootB = ProductCategoryModel::create(['name' => '水果', 'parent_id' => 0, 'sort' => 1, 'status' => 1]);
// 子分类 sort 与创建顺序相反,验证按 sort 而非 id 排序
$leafA1 = ProductCategoryModel::create(['name' => '叶菜', 'parent_id' => $rootA->id, 'sort' => 1, 'status' => 1]);
$leafA2 = ProductCategoryModel::create(['name' => '根茎', 'parent_id' => $rootA->id, 'sort' => 0, 'status' => 1]);
$pB = ProductModel::factory()->create(['category_id' => $rootB->id, 'status' => 1]);
$pA1 = ProductModel::factory()->create(['category_id' => $leafA1->id, 'status' => 1]);
$pA2Low = ProductModel::factory()->create(['category_id' => $leafA2->id, 'status' => 1, 'sort' => 1]);
$pA2High = ProductModel::factory()->create(['category_id' => $leafA2->id, 'status' => 1, 'sort' => 9]);
$pNone = ProductModel::factory()->create(['category_id' => 0, 'status' => 1]);
$ids = array_column($this->getJson('/mini/product/list')->assertOk()->json('data.data'), 'id');
// 树序:rootA → leafA2 → leafA1 → rootBleafA2 内 sort 升序;无分类排最后
$this->assertSame([$pA2Low->id, $pA2High->id, $pA1->id, $pB->id, $pNone->id], $ids);
}
/** 商品列表:登录门店附加购物车行ID与数量,响应附悬浮球汇总 */
public function test_product_list_appends_cart_quantity_and_summary(): void
{
[$store, $product] = $this->makeStoreWithProduct('5.00');
$extra = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '3.00']);
$this->actingAsMiniStore($store);
$this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 2]);
$this->postJson('/mini/cart', ['product_id' => $extra->id, 'quantity' => 1]);
$cartId = CartModel::where('product_id', $product->id)->first()->id;
$data = $this->getJson('/mini/product/list')->assertOk()->json('data');
$row = collect($data['data'])->firstWhere('id', $product->id);
$this->assertSame($cartId, $row['cart_id'], '在购物车的商品应附购物车行ID');
$this->assertSame('2.00', $row['cart_quantity']);
// 不在购物车的商品:cart_id=0、cart_quantity='0.00'
$other = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON, 'cost_price' => '1.00']);
$otherRow = collect($this->getJson('/mini/product/list')->json('data.data'))->firstWhere('id', $other->id);
$this->assertSame(0, $otherRow['cart_id']);
$this->assertSame('0.00', $otherRow['cart_quantity']);
// 悬浮球汇总:2 种商品、总数量 3、总金额 5×2+3×1=13
$this->assertSame(2, $data['cart']['total_count']);
$this->assertSame('3.00', $data['cart']['total_quantity']);
$this->assertSame('13.00', $data['cart']['total_amount']);
}
/** 商品列表/详情:未登录时购物车字段为零值结构 */
public function test_product_list_guest_sees_zero_cart_fields(): void
{
[, $product] = $this->makeStoreWithProduct();
$data = $this->getJson('/mini/product/list')->assertOk()->json('data');
$row = collect($data['data'])->firstWhere('id', $product->id);
$this->assertSame(0, $row['cart_id']);
$this->assertSame('0.00', $row['cart_quantity']);
$this->assertSame(
['total_count' => 0, 'total_quantity' => '0.00', 'total_amount' => '0.00'],
$data['cart']
);
$detail = $this->getJson("/mini/product/{$product->id}")->assertOk()->json('data');
$this->assertSame(0, $detail['cart_id']);
$this->assertSame('0.00', $detail['cart_quantity']);
}
/** 商品详情:登录门店附加购物车行ID与数量 */
public function test_product_detail_appends_cart_quantity(): void
{
[$store, $product] = $this->makeStoreWithProduct();
$this->actingAsMiniStore($store);
$this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 4]);
$cartId = CartModel::first()->id;
$data = $this->getJson("/mini/product/{$product->id}")->assertOk()->json('data');
$this->assertSame($cartId, $data['cart_id']);
$this->assertSame('4.00', $data['cart_quantity']);
}
/** 首页:登录门店附购物车悬浮球汇总,未登录为零值结构 */
public function test_home_appends_cart_summary(): void
{
[$store, $product] = $this->makeStoreWithProduct('5.00');
// 未登录:零值结构
$guest = $this->getJson('/mini/home')->assertOk()->json('data');
$this->assertSame(
['total_count' => 0, 'total_quantity' => '0.00', 'total_amount' => '0.00'],
$guest['cart']
);
// 登录:汇总跟随购物车
$this->actingAsMiniStore($store);
$this->postJson('/mini/cart', ['product_id' => $product->id, 'quantity' => 3]);
$data = $this->getJson('/mini/home')->assertOk()->json('data');
$this->assertSame(1, $data['cart']['total_count']);
$this->assertSame('3.00', $data['cart']['total_quantity']);
$this->assertSame('15.00', $data['cart']['total_amount']);
}
}