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 → rootB;leafA2 内 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']); } }