378 lines
20 KiB
PHP
378 lines
20 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\Merchant;
|
|
use App\Models\Product;
|
|
use App\Models\ProductCategory;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Tests\TestCase;
|
|
use function PHPUnit\Framework\assertStringContainsString;
|
|
|
|
class MerchantBusinessPagesTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function loginAsMerchantAdmin(): void
|
|
{
|
|
$this->seed();
|
|
|
|
$this->post('/merchant-admin/login', [
|
|
'email' => 'merchant.admin@demo.local',
|
|
'password' => 'Merchant@123456',
|
|
])->assertRedirect('/merchant-admin');
|
|
}
|
|
|
|
public function test_merchant_products_page_displays_import_and_history_entries(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$this->get('/merchant-admin/products?status=published&min_price=150&max_price=220&sort=price_desc')
|
|
->assertOk()
|
|
->assertSee('商家商品管理')
|
|
->assertSee('批量导入商品')
|
|
->assertSee('下载导入模板')
|
|
->assertSee('导出当前筛选结果 CSV')
|
|
->assertSee('进入完整导入历史页')
|
|
->assertSee('导入历史摘要')
|
|
->assertSee('当前筛选摘要')
|
|
->assertSee('运营关注项')
|
|
->assertSee('当前信号')
|
|
->assertSee('工作台导航')
|
|
->assertSee('已上架')
|
|
->assertSee('价格从高到低');
|
|
}
|
|
|
|
public function test_merchant_product_operations_focus_can_follow_current_filters(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$this->get('/merchant-admin/products?status=draft&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前正在查看草稿商品,建议优先补齐标题、分类、价格与库存后再安排上架。')
|
|
->assertSee('继续查看当前草稿');
|
|
|
|
$this->get('/merchant-admin/products?status=published&min_stock=0&max_stock=20&sort=stock_asc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架库存视角,建议优先确认低库存补货节奏,并同步观察高库存结构是否健康。')
|
|
->assertSee('继续查看当前库存视角');
|
|
|
|
$this->get('/merchant-admin/products?status=published&category_id=1&keyword=%E6%BC%94%E7%A4%BA&min_price=150&max_price=220&sort=price_desc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架的“默认分类”分类下关键词“演示”命中的价格带 ¥150.00 ~ ¥220.00 商品,建议优先核对在售商品命名、分类承接、价格梯度与搜索结果是否一致,并同步观察库存结构与转化表现是否健康。')
|
|
->assertSee('继续查看当前已上架分类关键词价格带商品')
|
|
->assertSee('去看当前已上架分类关键词商品');
|
|
|
|
$this->get('/merchant-admin/products?status=published&category_id=1&keyword=%E6%BC%94%E7%A4%BA&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架的“默认分类”分类下关键词“演示”命中的商品,建议优先核对在售商品命名、分类承接与搜索结果是否一致,并同步观察价格带与库存结构是否健康。')
|
|
->assertSee('继续查看当前已上架分类关键词商品');
|
|
|
|
$this->get('/merchant-admin/products?status=published&category_id=1&min_price=150&max_price=220&sort=price_desc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架的“默认分类”分类下价格带 ¥150.00 ~ ¥220.00 商品,建议优先核对该分类在售商品的价格结构、库存分布与转化表现是否协调。')
|
|
->assertSee('继续查看当前已上架分类价格带商品');
|
|
|
|
$this->get('/merchant-admin/products?status=published&keyword=%E6%BC%94%E7%A4%BA&min_price=150&max_price=220&sort=price_desc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架商品中关键词“演示”命中的价格带 ¥150.00 ~ ¥220.00 结果,建议优先核对在售商品命名、卖点表达与价格梯度是否一致,并同步观察库存结构、转化表现与搜索承接是否健康。')
|
|
->assertSee('继续查看当前已上架关键词价格带商品')
|
|
->assertSee('去看当前已上架关键词商品');
|
|
|
|
$this->get('/merchant-admin/products?status=published&keyword=%E6%BC%94%E7%A4%BA&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架商品中关键词“演示”命中的结果,建议优先核对在售商品命名、卖点表达与搜索承接是否一致,并同步观察价格带与库存结构是否健康。')
|
|
->assertSee('继续查看当前已上架关键词商品');
|
|
|
|
$this->get('/merchant-admin/products?category_id=1&keyword=%E6%BC%94%E7%A4%BA&min_price=150&max_price=220&sort=price_desc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦“默认分类”分类下关键词“演示”命中的价格带 ¥150.00 ~ ¥220.00 商品,建议优先核对分类承接、命名卖点与价格梯度是否一致,并同步观察库存结构、转化表现与搜索承接是否健康。')
|
|
->assertSee('继续查看当前分类关键词价格带商品')
|
|
->assertSee('去看当前分类关键词商品');
|
|
|
|
$this->get('/merchant-admin/products?category_id=1&keyword=%E6%BC%94%E7%A4%BA&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦“默认分类”分类下关键词“演示”命中的商品,建议优先核对分类承接、命名卖点与搜索结果是否一致,并同步观察相关商品的价格带与库存结构。')
|
|
->assertSee('继续查看当前分类关键词商品');
|
|
|
|
$this->get('/merchant-admin/products?status=published&category_id=1&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架的“默认分类”分类商品,建议优先核对该分类在售商品的价格带、库存结构与转化表现是否均衡。')
|
|
->assertSee('继续查看当前已上架分类商品');
|
|
|
|
$this->get('/merchant-admin/products?category_id=1&min_price=150&max_price=220&sort=price_desc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦“默认分类”分类下价格带 ¥150.00 ~ ¥220.00 商品,建议优先核对该分类价格结构是否连贯,并同步观察库存分布与转化表现是否健康。')
|
|
->assertSee('继续查看当前分类价格带商品');
|
|
|
|
$this->get('/merchant-admin/products?category_id=1&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦“默认分类”分类商品,建议优先核对分类承接是否准确,并同步观察价格带与库存结构是否均衡。')
|
|
->assertSee('继续查看当前分类商品');
|
|
|
|
$this->get('/merchant-admin/products?keyword=%E6%BC%94%E7%A4%BA&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦关键词“演示”命中的商品,建议优先核对命名、卖点与搜索承接是否一致,并同步观察相关商品的价格带与库存结构。')
|
|
->assertSee('继续查看当前关键词商品');
|
|
|
|
$this->get('/merchant-admin/products?min_price=150&max_price=220&sort=price_desc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦价格带 ¥150.00 ~ ¥220.00 商品,建议优先核对定价梯度是否连贯,并同步观察库存结构与转化表现是否匹配。')
|
|
->assertSee('继续查看当前价格带商品');
|
|
}
|
|
|
|
public function test_merchant_orders_page_displays_filters_and_export_entry(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$this->get('/merchant-admin/orders?status=shipped&payment_status=paid&platform=wechat_mini&device_type=mini-program&payment_channel=wechat_pay&min_pay_amount=180&max_pay_amount=190&sort=pay_amount_desc')
|
|
->assertOk()
|
|
->assertSee('商家订单管理')
|
|
->assertSee('筛选条件')
|
|
->assertSee('导出当前筛选结果 CSV')
|
|
->assertSee('当前筛选摘要')
|
|
->assertSee('运营关注项')
|
|
->assertSee('当前信号')
|
|
->assertSee('工作台导航')
|
|
->assertSee('已发货')
|
|
->assertSee('已支付')
|
|
->assertSee('微信小程序')
|
|
->assertSee('小程序环境')
|
|
->assertSee('微信支付')
|
|
->assertSee('订单汇总');
|
|
}
|
|
|
|
public function test_merchant_order_operations_focus_can_follow_current_filters(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$this->get('/merchant-admin/orders?platform=wechat_mini&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦微信小程序订单,建议优先关注下单到支付转化是否顺畅,并同步排查小程序端支付回流体验。')
|
|
->assertSee('继续查看微信小程序订单');
|
|
|
|
$this->get('/merchant-admin/orders?payment_channel=wechat_pay&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦微信支付订单,建议优先核对支付成功率、回调稳定性与失败重试转化。')
|
|
->assertSee('继续查看微信支付订单');
|
|
|
|
$this->get('/merchant-admin/orders?device_type=mini-program&payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦小程序环境订单,建议优先核对授权链路、支付唤起表现与下单回流是否顺畅。')
|
|
->assertSee('继续查看小程序环境订单');
|
|
|
|
$this->get('/merchant-admin/orders?device_type=mobile-webview&payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦微信内网页订单,建议优先关注授权静默登录、页面跳转稳定性与支付拉起后的回流体验。')
|
|
->assertSee('继续查看微信内网页订单');
|
|
|
|
$this->get('/merchant-admin/orders?device_type=mobile&payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦移动浏览器订单,建议优先关注 H5 下单链路、页面加载稳定性与支付转化流失点。')
|
|
->assertSee('继续查看移动浏览器订单');
|
|
|
|
$this->get('/merchant-admin/orders?device_type=desktop&payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦桌面浏览器订单,建议优先关注 PC 端下单流程、页面首屏稳定性与高客单转化表现。')
|
|
->assertSee('继续查看桌面浏览器订单');
|
|
|
|
$this->get('/merchant-admin/orders?payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦支付失败订单,建议优先排查支付渠道、回调结果与用户重试情况。')
|
|
->assertSee('继续查看支付失败订单');
|
|
|
|
$this->get('/merchant-admin/orders?status=completed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前正在查看已完成订单,建议复盘高客单成交与复购来源,沉淀更稳定的转化路径。')
|
|
->assertSee('继续查看已完成订单');
|
|
}
|
|
|
|
public function test_merchant_categories_and_users_pages_display_expected_content(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$this->get('/merchant-admin/product-categories')
|
|
->assertOk()
|
|
->assertSee('商家商品分类')
|
|
->assertSee('新增分类')
|
|
->assertSee('分类列表');
|
|
|
|
$this->get('/merchant-admin/users')
|
|
->assertOk()
|
|
->assertSee('商家用户管理')
|
|
->assertSee('用户列表');
|
|
}
|
|
|
|
public function test_merchant_import_histories_page_displays_filter_and_export_entries(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$this->get('/merchant-admin/products/import-histories')
|
|
->assertOk()
|
|
->assertSee('商家商品导入历史')
|
|
->assertSee('筛选导入历史')
|
|
->assertSee('导出当前筛选 CSV')
|
|
->assertSee('返回商品页');
|
|
}
|
|
|
|
public function test_merchant_product_summary_stats_match_export_summary_for_same_filters(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$page = $this->get('/merchant-admin/products?keyword=%E6%BC%94%E7%A4%BA%E5%95%86%E5%93%81&status=published&min_price=150&max_price=220');
|
|
$page->assertOk()->assertViewHas('summaryStats', function (array $summaryStats) {
|
|
return ($summaryStats['total_products'] ?? null) === 1
|
|
&& ($summaryStats['total_stock'] ?? null) === 100
|
|
&& (float) ($summaryStats['total_stock_value'] ?? 0) === 19900.0
|
|
&& (float) ($summaryStats['average_price'] ?? 0) === 199.0;
|
|
});
|
|
|
|
$export = $this->get('/merchant-admin/products/export?keyword=%E6%BC%94%E7%A4%BA%E5%95%86%E5%93%81&status=published&min_price=150&max_price=220');
|
|
$export->assertOk();
|
|
$content = $export->streamedContent();
|
|
|
|
assertStringContainsString('导出信息,商家商品导出', $content);
|
|
assertStringContainsString('状态,已上架', $content);
|
|
assertStringContainsString('最低价格,¥150.00', $content);
|
|
assertStringContainsString('最高价格,¥220.00', $content);
|
|
assertStringContainsString('导出商品数,1', $content);
|
|
assertStringContainsString('导出总库存,100', $content);
|
|
assertStringContainsString('导出总货值,19900.00', $content);
|
|
assertStringContainsString('导出平均售价,199.00', $content);
|
|
assertStringContainsString('演示商品', $content);
|
|
}
|
|
|
|
public function test_merchant_order_summary_stats_match_export_summary_for_same_filters(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$page = $this->get('/merchant-admin/orders?status=shipped&payment_status=paid&platform=wechat_mini&device_type=mini-program&payment_channel=wechat_pay&min_pay_amount=180&max_pay_amount=190');
|
|
$page->assertOk()->assertViewHas('summaryStats', function (array $summaryStats) {
|
|
return ($summaryStats['total_orders'] ?? null) === 1
|
|
&& (float) ($summaryStats['total_pay_amount'] ?? 0) === 189.0
|
|
&& (float) ($summaryStats['average_order_amount'] ?? 0) === 189.0
|
|
&& ($summaryStats['paid_orders'] ?? null) === 1
|
|
&& ($summaryStats['failed_payment_orders'] ?? null) === 0;
|
|
});
|
|
|
|
$export = $this->get('/merchant-admin/orders/export?status=shipped&payment_status=paid&platform=wechat_mini&device_type=mini-program&payment_channel=wechat_pay&min_pay_amount=180&max_pay_amount=190');
|
|
$export->assertOk();
|
|
$content = $export->streamedContent();
|
|
|
|
assertStringContainsString('导出信息,商家订单导出', $content);
|
|
assertStringContainsString('订单状态,已发货', $content);
|
|
assertStringContainsString('支付状态,已支付', $content);
|
|
assertStringContainsString('平台,微信小程序', $content);
|
|
assertStringContainsString('设备类型,小程序环境', $content);
|
|
assertStringContainsString('支付渠道,微信支付', $content);
|
|
assertStringContainsString('最低实付金额,¥180.00', $content);
|
|
assertStringContainsString('最高实付金额,¥190.00', $content);
|
|
assertStringContainsString('导出订单数,1', $content);
|
|
assertStringContainsString('导出实付总额,189.00', $content);
|
|
assertStringContainsString('导出平均客单价,189.00', $content);
|
|
assertStringContainsString('导出已支付订单数,1', $content);
|
|
assertStringContainsString('导出支付失败订单,0', $content);
|
|
assertStringContainsString('ORD202603080003', $content);
|
|
}
|
|
|
|
public function test_merchant_products_batch_update_blocks_out_of_scope_products(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$merchant = Merchant::query()->firstOrFail();
|
|
$foreignMerchant = Merchant::query()->create([
|
|
'name' => '外部站点',
|
|
'slug' => 'foreign-merchant-demo',
|
|
'domain' => null,
|
|
'contact_name' => '测试人',
|
|
'contact_phone' => '13800000002',
|
|
'contact_email' => 'foreign@example.com',
|
|
'plan' => 'basic',
|
|
'status' => 'active',
|
|
'activated_at' => now(),
|
|
'settings' => ['currency' => 'CNY'],
|
|
]);
|
|
|
|
$foreignCategory = ProductCategory::query()->create([
|
|
'merchant_id' => $foreignMerchant->id,
|
|
'name' => '外部分类',
|
|
'slug' => 'foreign-default',
|
|
'status' => 'active',
|
|
'sort' => 10,
|
|
'description' => '用于越权测试',
|
|
]);
|
|
|
|
$foreignProduct = Product::query()->create([
|
|
'merchant_id' => $foreignMerchant->id,
|
|
'category_id' => $foreignCategory->id,
|
|
'title' => '外部商品',
|
|
'slug' => 'foreign-product',
|
|
'sku' => 'SKU-FOREIGN-001',
|
|
'summary' => '用于越权测试',
|
|
'content' => 'test',
|
|
'price' => 66,
|
|
'original_price' => 88,
|
|
'stock' => 12,
|
|
'status' => 'published',
|
|
'images' => [],
|
|
]);
|
|
|
|
$localProduct = Product::query()->forMerchant($merchant->id)->orderBy('id')->firstOrFail();
|
|
|
|
$response = $this->from('/merchant-admin/products')->post('/merchant-admin/products/batch', [
|
|
'product_ids' => [$localProduct->id, $foreignProduct->id],
|
|
'action' => 'change_status',
|
|
'status' => 'offline',
|
|
]);
|
|
|
|
$response->assertRedirect('/merchant-admin/products');
|
|
$response->assertSessionHasErrors(['product_ids']);
|
|
$response->assertSessionHasErrors(['product_ids' => '勾选商品中存在越权或已删除的数据,请刷新后重试。']);
|
|
|
|
$this->assertSame('published', $localProduct->fresh()->status);
|
|
$this->assertSame('published', $foreignProduct->fresh()->status);
|
|
}
|
|
|
|
public function test_merchant_products_batch_change_category_blocks_invalid_category(): void
|
|
{
|
|
$this->loginAsMerchantAdmin();
|
|
|
|
$foreignMerchant = Merchant::query()->create([
|
|
'name' => '外部分站',
|
|
'slug' => 'foreign-category-merchant-demo',
|
|
'domain' => null,
|
|
'contact_name' => '测试人',
|
|
'contact_phone' => '13800000003',
|
|
'contact_email' => 'foreign-category@example.com',
|
|
'plan' => 'basic',
|
|
'status' => 'active',
|
|
'activated_at' => now(),
|
|
'settings' => ['currency' => 'CNY'],
|
|
]);
|
|
|
|
$foreignCategory = ProductCategory::query()->create([
|
|
'merchant_id' => $foreignMerchant->id,
|
|
'name' => '外部分站分类',
|
|
'slug' => 'foreign-category',
|
|
'status' => 'active',
|
|
'sort' => 10,
|
|
'description' => '用于非法分类测试',
|
|
]);
|
|
|
|
$product = Product::query()->where('merchant_id', Merchant::query()->firstOrFail()->id)->orderBy('id')->firstOrFail();
|
|
$originalCategoryId = $product->category_id;
|
|
|
|
$response = $this->from('/merchant-admin/products')->post('/merchant-admin/products/batch', [
|
|
'product_ids' => [$product->id],
|
|
'action' => 'change_category',
|
|
'category_id' => $foreignCategory->id,
|
|
]);
|
|
|
|
$response->assertRedirect('/merchant-admin/products');
|
|
$response->assertSessionHasErrors(['category_id']);
|
|
$response->assertSessionHasErrors(['category_id' => '所选分类不存在或不属于当前商家。']);
|
|
|
|
$this->assertSame($originalCategoryId, $product->fresh()->category_id);
|
|
}
|
|
}
|