推荐商品
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\HomeSpecialModel;
|
||||
use App\Models\ProductModel;
|
||||
|
||||
/**
|
||||
* 后台特价推荐(客户端配置):批量添加(重复自动跳过)、商品名搜索、
|
||||
* 编辑排序/状态、单个/批量删除、权限点拦截
|
||||
*/
|
||||
class ClientSpecialTest extends ProcurementTestCase
|
||||
{
|
||||
/** 批量添加成功:全部写入推荐表,added/skipped 计数正确 */
|
||||
public function test_batch_add_creates_specials(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$ids = ProductModel::factory()->count(3)->create()->pluck('id')->all();
|
||||
|
||||
$this->postJson('/client/special/batch', ['product_ids' => $ids])
|
||||
->assertOk()
|
||||
->assertJsonPath('success', true)
|
||||
->assertJsonPath('data.added', 3)
|
||||
->assertJsonPath('data.skipped', 0);
|
||||
|
||||
foreach ($ids as $id) {
|
||||
$this->assertDatabaseHas('mini_home_special', [
|
||||
'product_id' => $id,
|
||||
'status' => HomeSpecialModel::STATUS_NORMAL,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/** 重复添加:已在推荐中的商品自动跳过,不产生重复行 */
|
||||
public function test_batch_add_skips_existing_products(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
[$a, $b] = ProductModel::factory()->count(2)->create()->all();
|
||||
HomeSpecialModel::create(['product_id' => $a->id, 'sort' => 0, 'status' => 1]);
|
||||
|
||||
$this->postJson('/client/special/batch', ['product_ids' => [$a->id, $b->id]])
|
||||
->assertOk()
|
||||
->assertJsonPath('success', true)
|
||||
->assertJsonPath('data.added', 1)
|
||||
->assertJsonPath('data.skipped', 1);
|
||||
|
||||
$this->assertSame(1, HomeSpecialModel::where('product_id', $a->id)->count());
|
||||
$this->assertSame(2, HomeSpecialModel::count());
|
||||
}
|
||||
|
||||
/** product_ids 为空数组 → 校验失败,不添加任何推荐 */
|
||||
public function test_batch_add_requires_non_empty_product_ids(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
ProductModel::factory()->create();
|
||||
|
||||
$this->postJson('/client/special/batch', ['product_ids' => []])
|
||||
->assertOk()
|
||||
->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', '请选择要添加的商品');
|
||||
|
||||
$this->assertSame(0, HomeSpecialModel::count());
|
||||
}
|
||||
|
||||
/** 无 client.special.create 权限点 → 拦截 */
|
||||
public function test_batch_add_forbidden_without_permission(): void
|
||||
{
|
||||
// 先建占位用户:每个测试方法内首个系统用户自增 id=1,超管旁路会绕过 abilities 校验
|
||||
$this->actingAsSysUser();
|
||||
$this->actingAsSysUser(['client.special.query']);
|
||||
|
||||
$product = ProductModel::factory()->create();
|
||||
|
||||
$this->postJson('/client/special/batch', ['product_ids' => [$product->id]])
|
||||
->assertOk()
|
||||
->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', 'No Permission');
|
||||
|
||||
$this->assertSame(0, HomeSpecialModel::count());
|
||||
}
|
||||
|
||||
/** 列表 keyword 按商品名模糊过滤,行内附带商品信息 */
|
||||
public function test_query_filters_by_product_name_keyword(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$cabbage = ProductModel::factory()->create(['name' => '有机大白菜']);
|
||||
$apple = ProductModel::factory()->create(['name' => '红富士苹果']);
|
||||
HomeSpecialModel::create(['product_id' => $cabbage->id, 'sort' => 0, 'status' => 1]);
|
||||
HomeSpecialModel::create(['product_id' => $apple->id, 'sort' => 0, 'status' => 1]);
|
||||
|
||||
$data = $this->getJson('/client/special?keyword=' . urlencode('白菜'))
|
||||
->assertOk()
|
||||
->json('data');
|
||||
|
||||
$this->assertSame(1, $data['total']);
|
||||
$this->assertSame('有机大白菜', $data['data'][0]['product']['name']);
|
||||
}
|
||||
|
||||
/** 编辑:仅排序与状态可改 */
|
||||
public function test_update_changes_sort_and_status(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$special = HomeSpecialModel::create([
|
||||
'product_id' => ProductModel::factory()->create()->id,
|
||||
'sort' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$this->putJson("/client/special/{$special->id}", ['sort' => 5, 'status' => 0])
|
||||
->assertOk()
|
||||
->assertJsonPath('success', true);
|
||||
|
||||
$this->assertDatabaseHas('mini_home_special', [
|
||||
'id' => $special->id,
|
||||
'sort' => 5,
|
||||
'status' => 0,
|
||||
]);
|
||||
}
|
||||
|
||||
/** 单个删除 + 批量删除:选中的删除,未选中的保留 */
|
||||
public function test_delete_and_batch_delete(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$products = ProductModel::factory()->count(3)->create();
|
||||
$specials = $products->map(static fn ($p) => HomeSpecialModel::create([
|
||||
'product_id' => $p->id, 'sort' => 0, 'status' => 1,
|
||||
]));
|
||||
|
||||
$this->deleteJson("/client/special/{$specials[0]->id}")
|
||||
->assertOk()
|
||||
->assertJsonPath('success', true);
|
||||
$this->assertDatabaseMissing('mini_home_special', ['id' => $specials[0]->id]);
|
||||
|
||||
$this->deleteJson('/client/special/batch', ['ids' => [$specials[1]->id]])
|
||||
->assertOk()
|
||||
->assertJsonPath('success', true);
|
||||
|
||||
$this->assertDatabaseMissing('mini_home_special', ['id' => $specials[1]->id]);
|
||||
$this->assertDatabaseHas('mini_home_special', ['id' => $specials[2]->id]);
|
||||
}
|
||||
|
||||
/** 批量删除 ids 为空 → 校验失败 */
|
||||
public function test_batch_delete_requires_non_empty_ids(): void
|
||||
{
|
||||
$this->actingAsSysUser();
|
||||
$special = HomeSpecialModel::create([
|
||||
'product_id' => ProductModel::factory()->create()->id,
|
||||
'sort' => 0,
|
||||
'status' => 1,
|
||||
]);
|
||||
|
||||
$this->deleteJson('/client/special/batch', ['ids' => []])
|
||||
->assertOk()
|
||||
->assertJsonPath('success', false)
|
||||
->assertJsonPath('msg', '请选择要删除的推荐');
|
||||
|
||||
$this->assertDatabaseHas('mini_home_special', ['id' => $special->id]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\CartModel;
|
||||
use App\Models\CustomerLevelModel;
|
||||
use App\Models\HomeSpecialModel;
|
||||
use App\Models\ProductModel;
|
||||
use App\Models\StoreModel;
|
||||
|
||||
/**
|
||||
* 小程序特价推荐列表:免登录浏览 price=null、登录门店按等级上浮换算价、
|
||||
* 停用推荐/下架商品不展示、按推荐排序升序、购物车数量随行输出
|
||||
*/
|
||||
class MiniSpecialTest extends ProcurementTestCase
|
||||
{
|
||||
/**
|
||||
* 造门店 + 上架商品 + 正常推荐
|
||||
*
|
||||
* @return array{0: StoreModel, 1: ProductModel, 2: CustomerLevelModel, 3: HomeSpecialModel}
|
||||
*/
|
||||
private function makeSpecial(string $price = '5.00', int $sort = 0): array
|
||||
{
|
||||
$level = CustomerLevelModel::factory()->create();
|
||||
$store = StoreModel::factory()->create(['level_id' => $level->id]);
|
||||
$product = ProductModel::factory()->create([
|
||||
'status' => ProductModel::STATUS_ON,
|
||||
'cost_price' => $price,
|
||||
]);
|
||||
$special = HomeSpecialModel::create([
|
||||
'product_id' => $product->id,
|
||||
'sort' => $sort,
|
||||
'status' => HomeSpecialModel::STATUS_NORMAL,
|
||||
]);
|
||||
|
||||
return [$store, $product, $level, $special];
|
||||
}
|
||||
|
||||
/** 游客:返回推荐商品,price=null,成本价不输出 */
|
||||
public function test_guest_sees_specials_with_null_price(): void
|
||||
{
|
||||
[, $product] = $this->makeSpecial();
|
||||
|
||||
$data = $this->getJson('/mini/special/list')->assertOk()->json('data');
|
||||
|
||||
$this->assertSame(1, $data['total']);
|
||||
$row = $data['data'][0];
|
||||
$this->assertSame($product->name, $row['name']);
|
||||
$this->assertNull($row['price'], '未登录不显示价格');
|
||||
$this->assertArrayNotHasKey('cost_price', $row, '成本价不得输出到小程序端');
|
||||
}
|
||||
|
||||
/** 登录门店:按等级上浮比例换算价(成本价 20 上浮 30% = 26.00) */
|
||||
public function test_store_sees_level_price(): void
|
||||
{
|
||||
[$store, $product, $level] = $this->makeSpecial('20.00');
|
||||
$level->update(['percent' => 30]);
|
||||
$this->actingAsMiniStore($store);
|
||||
|
||||
$row = $this->getJson('/mini/special/list')->assertOk()->json('data.data.0');
|
||||
|
||||
$this->assertSame($product->id, $row['id']);
|
||||
$this->assertSame('26.00', $row['price']);
|
||||
}
|
||||
|
||||
/** 停用推荐与下架商品的推荐不展示 */
|
||||
public function test_disabled_special_and_off_shelf_product_excluded(): void
|
||||
{
|
||||
[, $productOn] = $this->makeSpecial();
|
||||
|
||||
$offProduct = ProductModel::factory()->create(['status' => ProductModel::STATUS_OFF]);
|
||||
HomeSpecialModel::create(['product_id' => $offProduct->id, 'sort' => 0, 'status' => 1]);
|
||||
|
||||
$disabledProduct = ProductModel::factory()->create(['status' => ProductModel::STATUS_ON]);
|
||||
HomeSpecialModel::create(['product_id' => $disabledProduct->id, 'sort' => 0, 'status' => 0]);
|
||||
|
||||
$data = $this->getJson('/mini/special/list')->assertOk()->json('data');
|
||||
|
||||
$this->assertSame(1, $data['total']);
|
||||
$this->assertSame($productOn->id, $data['data'][0]['id']);
|
||||
}
|
||||
|
||||
/** 按推荐排序 sort 升序(越小越靠前) */
|
||||
public function test_specials_sorted_by_sort_asc(): void
|
||||
{
|
||||
[, $productB] = $this->makeSpecial('5.00', 5);
|
||||
[, $productA] = $this->makeSpecial('6.00', 1);
|
||||
|
||||
$rows = $this->getJson('/mini/special/list')->assertOk()->json('data.data');
|
||||
|
||||
$this->assertSame($productA->id, $rows[0]['id'], 'sort=1 应排在 sort=5 前');
|
||||
$this->assertSame($productB->id, $rows[1]['id']);
|
||||
}
|
||||
|
||||
/** 登录门店:购物车行 ID 与数量随行输出(供列表直接加减) */
|
||||
public function test_cart_quantity_attached_for_store(): void
|
||||
{
|
||||
[$store, $product] = $this->makeSpecial();
|
||||
CartModel::create([
|
||||
'store_id' => $store->id,
|
||||
'product_id' => $product->id,
|
||||
'quantity' => '2.00',
|
||||
]);
|
||||
$this->actingAsMiniStore($store);
|
||||
|
||||
$row = $this->getJson('/mini/special/list')->assertOk()->json('data.data.0');
|
||||
|
||||
$this->assertGreaterThan(0, $row['cart_id']);
|
||||
$this->assertSame('2.00', $row['cart_quantity']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user