349 lines
18 KiB
PHP
349 lines
18 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 AdminBusinessPagesTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function loginAsPlatformAdmin(): void
|
|
{
|
|
$this->seed();
|
|
|
|
$this->post('/admin/login', [
|
|
'email' => 'platform.admin@demo.local',
|
|
'password' => 'Platform@123456',
|
|
])->assertRedirect('/admin');
|
|
}
|
|
|
|
public function test_admin_merchants_page_displays_site_management_content(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$this->get('/admin/merchants')
|
|
->assertOk()
|
|
->assertSee('站点管理')
|
|
->assertSee('新增站点')
|
|
->assertSee('站点列表')
|
|
->assertSee('创建站点');
|
|
}
|
|
|
|
public function test_admin_orders_page_displays_filters_and_export_entry(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$this->get('/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_admin_order_operations_focus_can_follow_current_filters(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$this->get('/admin/orders?platform=wechat_mini&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦微信小程序订单,建议优先关注下单到支付转化是否顺畅,并同步排查小程序端支付回流体验。')
|
|
->assertSee('继续查看微信小程序订单');
|
|
|
|
$this->get('/admin/orders?payment_channel=wechat_pay&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦微信支付订单,建议优先核对支付成功率、回调稳定性与失败重试转化。')
|
|
->assertSee('继续查看微信支付订单');
|
|
|
|
$this->get('/admin/orders?device_type=mini-program&payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦小程序环境订单,建议优先核对授权链路、支付唤起表现与下单回流是否顺畅。')
|
|
->assertSee('继续查看小程序环境订单');
|
|
|
|
$this->get('/admin/orders?device_type=mobile-webview&payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦微信内网页订单,建议优先关注授权静默登录、页面跳转稳定性与支付拉起后的回流体验。')
|
|
->assertSee('继续查看微信内网页订单');
|
|
|
|
$this->get('/admin/orders?device_type=mobile&payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦移动浏览器订单,建议优先关注 H5 下单链路、页面加载稳定性与支付转化流失点。')
|
|
->assertSee('继续查看移动浏览器订单');
|
|
|
|
$this->get('/admin/orders?device_type=desktop&payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦桌面浏览器订单,建议优先关注 PC 端下单流程、页面首屏稳定性与高客单转化表现。')
|
|
->assertSee('继续查看桌面浏览器订单');
|
|
|
|
$this->get('/admin/orders?payment_status=failed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦支付失败订单,建议优先排查支付渠道、回调结果与用户重试情况。')
|
|
->assertSee('继续查看支付失败订单');
|
|
|
|
$this->get('/admin/orders?status=completed&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前正在查看已完成订单,建议复盘高客单成交与复购来源,沉淀更稳定的转化路径。')
|
|
->assertSee('继续查看已完成订单');
|
|
}
|
|
|
|
public function test_admin_products_page_displays_import_and_history_entries(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$this->get('/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_admin_product_operations_focus_can_follow_current_filters(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$this->get('/admin/products?status=draft&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前正在查看草稿商品,建议优先补齐标题、分类、价格与库存后再安排上架。')
|
|
->assertSee('继续查看当前草稿');
|
|
|
|
$this->get('/admin/products?status=published&min_stock=0&max_stock=20&sort=stock_asc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架库存视角,建议优先确认低库存补货节奏,并同步观察高库存结构是否健康。')
|
|
->assertSee('继续查看当前库存视角');
|
|
|
|
$this->get('/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('/admin/products?status=published&category_id=1&keyword=%E6%BC%94%E7%A4%BA&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架的“演示店铺 / 默认分类”分类下关键词“演示”命中的商品,建议优先核对在售商品命名、分类承接与站点承接是否一致,并同步观察价格带、库存结构与站点覆盖是否健康。')
|
|
->assertSee('继续查看当前已上架分类关键词商品');
|
|
|
|
$this->get('/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('/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('/admin/products?status=published&keyword=%E6%BC%94%E7%A4%BA&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架商品中关键词“演示”命中的结果,建议优先核对在售商品命名、卖点表达与站点承接是否一致,并同步观察价格带、库存结构与站点覆盖是否健康。')
|
|
->assertSee('继续查看当前已上架关键词商品');
|
|
|
|
$this->get('/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('/admin/products?category_id=1&keyword=%E6%BC%94%E7%A4%BA&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦“演示店铺 / 默认分类”分类下关键词“演示”命中的商品,建议优先核对分类承接、命名卖点与站点承接是否一致,并同步观察相关商品的价格带、库存结构与站点覆盖。')
|
|
->assertSee('继续查看当前分类关键词商品');
|
|
|
|
$this->get('/admin/products?status=published&category_id=1&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦已上架的“演示店铺 / 默认分类”分类商品,建议优先核对该分类在售商品的价格带、库存结构与站点覆盖是否均衡。')
|
|
->assertSee('继续查看当前已上架分类商品');
|
|
|
|
$this->get('/admin/products?category_id=1&min_price=150&max_price=220&sort=price_desc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦“演示店铺 / 默认分类”分类下价格带 ¥150.00 ~ ¥220.00 商品,建议优先核对该分类价格结构是否连贯,并同步观察库存分布、转化表现与站点覆盖是否健康。')
|
|
->assertSee('继续查看当前分类价格带商品');
|
|
|
|
$this->get('/admin/products?category_id=1&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦“演示店铺 / 默认分类”分类商品,建议优先核对分类承接是否准确,并同步观察价格带、库存结构与站点覆盖是否均衡。')
|
|
->assertSee('继续查看当前分类商品');
|
|
|
|
$this->get('/admin/products?keyword=%E6%BC%94%E7%A4%BA&sort=latest')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦关键词“演示”命中的商品,建议优先核对命名、卖点与站点承接是否一致,并同步观察相关商品的价格带与库存结构。')
|
|
->assertSee('继续查看当前关键词商品');
|
|
|
|
$this->get('/admin/products?min_price=150&max_price=220&sort=price_desc')
|
|
->assertOk()
|
|
->assertSee('当前筛选已聚焦价格带 ¥150.00 ~ ¥220.00 商品,建议优先核对定价梯度是否连贯,并同步观察库存结构、转化表现与站点覆盖是否匹配。')
|
|
->assertSee('继续查看当前价格带商品');
|
|
}
|
|
|
|
public function test_admin_import_histories_page_displays_filter_and_export_entries(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$this->get('/admin/products/import-histories')
|
|
->assertOk()
|
|
->assertSee('商品导入历史')
|
|
->assertSee('筛选导入历史')
|
|
->assertSee('导出 CSV');
|
|
}
|
|
|
|
public function test_admin_product_summary_stats_match_export_summary_for_same_filters(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$page = $this->get('/admin/products?merchant_id=1&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('/admin/products/export?merchant_id=1&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('站点,"1 / 演示店铺"', $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_admin_order_summary_stats_match_export_summary_for_same_filters(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$page = $this->get('/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('/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_admin_products_batch_status_update_changes_selected_products(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$products = Product::query()->orderBy('id')->take(2)->get();
|
|
|
|
$this->post('/admin/products/batch', [
|
|
'product_ids' => $products->pluck('id')->all(),
|
|
'action' => 'change_status',
|
|
'status' => 'offline',
|
|
])->assertRedirect('/admin/products')
|
|
->assertSessionHas('success', '批量操作已完成,本次更新 2 条商品。');
|
|
|
|
foreach ($products as $product) {
|
|
$this->assertSame('offline', $product->fresh()->status);
|
|
}
|
|
}
|
|
|
|
public function test_admin_products_batch_change_category_blocks_cross_merchant_operation(): void
|
|
{
|
|
$this->loginAsPlatformAdmin();
|
|
|
|
$secondMerchant = Merchant::query()->create([
|
|
'name' => '第二站点',
|
|
'slug' => 'merchant-2-demo',
|
|
'domain' => null,
|
|
'contact_name' => '测试人',
|
|
'contact_phone' => '13800000001',
|
|
'contact_email' => 'merchant2@example.com',
|
|
'plan' => 'basic',
|
|
'status' => 'active',
|
|
'activated_at' => now(),
|
|
'settings' => ['currency' => 'CNY'],
|
|
]);
|
|
|
|
$secondCategory = ProductCategory::query()->create([
|
|
'merchant_id' => $secondMerchant->id,
|
|
'name' => '第二站点分类',
|
|
'slug' => 'merchant-2-default',
|
|
'status' => 'active',
|
|
'sort' => 10,
|
|
'description' => '用于跨商家分类拦截测试',
|
|
]);
|
|
|
|
$secondProduct = Product::query()->create([
|
|
'merchant_id' => $secondMerchant->id,
|
|
'category_id' => $secondCategory->id,
|
|
'title' => '第二站点商品',
|
|
'slug' => 'merchant-2-product',
|
|
'sku' => 'SKU-MERCHANT-2-001',
|
|
'summary' => '用于跨商家批量改分类测试',
|
|
'content' => 'test',
|
|
'price' => 88,
|
|
'original_price' => 99,
|
|
'stock' => 10,
|
|
'status' => 'published',
|
|
'images' => [],
|
|
]);
|
|
|
|
$firstProduct = Product::query()->orderBy('id')->firstOrFail();
|
|
$firstMerchantCategory = ProductCategory::query()->where('merchant_id', $firstProduct->merchant_id)->orderBy('id')->firstOrFail();
|
|
|
|
$response = $this->from('/admin/products')->post('/admin/products/batch', [
|
|
'product_ids' => [$firstProduct->id, $secondProduct->id],
|
|
'action' => 'change_category',
|
|
'category_id' => $firstMerchantCategory->id,
|
|
]);
|
|
|
|
$response->assertRedirect('/admin/products');
|
|
$response->assertSessionHasErrors(['category_id']);
|
|
$this->assertStringContainsString('所选分类不属于商家 #', session('errors')->first('category_id'));
|
|
|
|
$this->assertSame($secondCategory->id, $secondProduct->fresh()->category_id);
|
|
}
|
|
}
|