CaiWuXiTong/tests/Feature/Business/ThirdPartyWarehouseStoragePageTest.php

259 lines
10 KiB
PHP

<?php
namespace Tests\Feature\Business;
use App\Models\Auth\Permission;
use Illuminate\Support\Carbon;
use Mockery;
use Modules\EcommerceFinance\Repositories\ThirdPartyWarehouseStorageMirrorRepository;
use Modules\EcommerceFinance\Services\ThirdPartyWarehouseStorageSettlementService;
use Tests\Feature\FeatureTestCase;
class ThirdPartyWarehouseStoragePageTest extends FeatureTestCase
{
protected function setUp(): void
{
parent::setUp();
$settlement = Mockery::mock(ThirdPartyWarehouseStorageSettlementService::class);
$settlement->shouldReceive('settle')->zeroOrMoreTimes()->andReturn([
'ready' => false,
'complete' => false,
'message' => null,
'inbound_fee' => null,
'outbound_fee' => null,
'storage_fee' => null,
'total_fee' => null,
]);
$this->app->instance(ThirdPartyWarehouseStorageSettlementService::class, $settlement);
}
protected function tearDown(): void
{
Carbon::setTestNow();
parent::tearDown();
}
public function test_page_passes_a_daily_period_and_filters_to_the_mirror(): void
{
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
setting()->set('default.list_limit', 25);
$filters = [
'dimension' => 'day',
'period' => '2026-08-15',
'sku_code' => 'BG002950N956V01',
'product_name' => '餐椅',
'supplier' => '测试供应商',
'category' => '家具',
];
$repository = Mockery::mock(ThirdPartyWarehouseStorageMirrorRepository::class);
$repository->shouldReceive('available')->once()->andReturnTrue();
$repository->shouldReceive('ready')->once()->andReturnTrue();
$repository->shouldReceive('paginate')->once()->with($filters, 1, 25)->andReturn([
'data' => [$this->row()],
'total' => 1,
'summary' => [
'count' => 1,
'opening_qty' => 0,
'closing_qty' => 10,
],
'state' => ['status' => 'succeeded'],
'sync' => [
'code' => 'healthy',
'last_succeeded_at' => '2026-09-11 03:20:00',
'lag_minutes' => 10,
],
]);
$this->app->instance(ThirdPartyWarehouseStorageMirrorRepository::class, $repository);
$this->loginAs();
$this->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'third-party-warehouse-storage',
]) . '?' . http_build_query($filters))
->assertOk()
->assertSee('三方仓进销存')
->assertSee('查询维度:商品 + 按天')
->assertSee('value="day" checked', false)
->assertSee('type="date"', false)
->assertSee('value="2026-08-15"', false)
->assertSee('BG002950N956V01')
->assertSee('测试供应商')
->assertSee('每日 00:00 自动同步');
}
public function test_invalid_daily_period_falls_back_to_the_previous_complete_day(): void
{
Carbon::setTestNow('2026-09-11 16:00:00');
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
$repository = Mockery::mock(ThirdPartyWarehouseStorageMirrorRepository::class);
$repository->shouldReceive('available')->once()->andReturnTrue();
$repository->shouldReceive('ready')->once()->andReturnTrue();
$repository->shouldReceive('paginate')->once()->with(
Mockery::on(static fn (array $filters): bool => $filters['dimension'] === 'day'
&& $filters['period'] === '2026-09-10'),
1,
Mockery::type('int'),
)->andReturn($this->emptyResult());
$this->app->instance(ThirdPartyWarehouseStorageMirrorRepository::class, $repository);
$this->loginAs();
$this->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'third-party-warehouse-storage',
'dimension' => 'day',
'period' => '2026-02-31',
]))->assertOk()
->assertSee('value="2026-09-10"', false);
}
public function test_period_before_supported_start_date_is_clamped(): void
{
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
$repository = Mockery::mock(ThirdPartyWarehouseStorageMirrorRepository::class);
$repository->shouldReceive('available')->twice()->andReturnTrue();
$repository->shouldReceive('ready')->twice()->andReturnTrue();
$repository->shouldReceive('paginate')->once()->with(
Mockery::on(static fn (array $filters): bool => $filters['dimension'] === 'day'
&& $filters['period'] === '2026-08-01'),
1,
Mockery::type('int'),
)->andReturn($this->emptyResult());
$repository->shouldReceive('paginate')->once()->with(
Mockery::on(static fn (array $filters): bool => $filters['dimension'] === 'month'
&& $filters['period'] === '2026-08'),
1,
Mockery::type('int'),
)->andReturn($this->emptyResult());
$this->app->instance(ThirdPartyWarehouseStorageMirrorRepository::class, $repository);
$this->loginAs();
$route = route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'third-party-warehouse-storage',
]);
$this->get($route . '?dimension=day&period=2026-07-31')
->assertOk()
->assertSee('value="2026-08-01"', false);
$this->get($route . '?dimension=month&period=2026-07')
->assertOk()
->assertSee('value="2026-08"', false);
}
public function test_export_keeps_daily_filters_and_only_accepts_declared_fields(): void
{
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
$filters = [
'dimension' => 'day',
'period' => '2026-08-15',
'sku_code' => 'BG002950N956V01',
'product_name' => '',
'supplier' => '',
'category' => '',
];
$repository = Mockery::mock(ThirdPartyWarehouseStorageMirrorRepository::class);
$repository->shouldReceive('available')->once()->andReturnTrue();
$repository->shouldReceive('ready')->once()->andReturnTrue();
$repository->shouldReceive('exportRows')->once()->with($filters)->andReturn((static function (): \Generator {
yield self::row();
})());
$this->app->instance(ThirdPartyWarehouseStorageMirrorRepository::class, $repository);
$response = $this->loginAs()->get(route(
'business.courier-payables.third-party-warehouse-storage.export',
$filters + ['fields' => 'sku_code,closing_qty,not_allowed'],
));
$response->assertOk();
$this->assertStringContainsString('text/csv', (string) $response->headers->get('content-type'));
$content = $response->streamedContent();
$this->assertStringStartsWith(chr(0xEF) . chr(0xBB) . chr(0xBF), $content);
$this->assertStringContainsString('商品编码,期末数量', $content);
$this->assertStringContainsString("'BG002950N956V01,10", $content);
$this->assertStringNotContainsString('货主', $content);
$this->assertStringNotContainsString('not_allowed', $content);
}
public function test_page_explains_that_an_uninitialized_mirror_is_not_empty_business_data(): void
{
$this->user->attachPermission(Permission::where('name', 'read-admin-panel')->firstOrFail());
$repository = Mockery::mock(ThirdPartyWarehouseStorageMirrorRepository::class);
$repository->shouldReceive('available')->once()->andReturnTrue();
$repository->shouldReceive('ready')->once()->andReturnFalse();
$repository->shouldReceive('state')->once()->andReturn(['status' => 'pending']);
$repository->shouldReceive('syncStatus')->once()->andReturn([
'code' => 'pending',
'last_succeeded_at' => null,
'lag_minutes' => null,
]);
$repository->shouldNotReceive('paginate');
$this->app->instance(ThirdPartyWarehouseStorageMirrorRepository::class, $repository);
$this->loginAs();
$this->get(route('business.pages.show', [
'section' => 'courier-payables',
'page' => 'third-party-warehouse-storage',
]))->assertOk()
->assertSee('首次同步尚未完成')
->assertSee('不会显示空白数据冒充正式结果');
}
/** @return array<string,mixed> */
private static function row(): array
{
return [
'owner' => '常州简尚家居有限公司',
'date' => '2026-08-15',
'sku_code' => 'BG002950N956V01',
'image_url' => '',
'product_name' => '测试餐椅',
'short_name' => '餐椅',
'supplier' => '测试供应商',
'category' => '家具',
'virtual_category' => '',
'supplier_style_no' => 'SUP-001',
'color_spec' => '原木色',
'brand' => '测试品牌',
'unit' => '件',
'opening_qty' => 0,
'closing_qty' => 10,
'processing_in' => 0,
'processing_out' => 0,
'purchase_in' => 34,
'purchase_return' => 0,
'other_in' => 0,
'other_return' => 0,
'sales_out' => -24,
'sales_return' => 0,
'stocktake' => 0,
'transfer_out' => 0,
'transfer_in' => 0,
'opening_aux' => 0,
'other_out' => 0,
];
}
/** @return array<string,mixed> */
private function emptyResult(): array
{
return [
'data' => [],
'total' => 0,
'summary' => [
'count' => 0,
'opening_qty' => 0,
'closing_qty' => 0,
],
'state' => ['status' => 'succeeded'],
'sync' => [
'code' => 'healthy',
'last_succeeded_at' => '2026-09-11 03:20:00',
'lag_minutes' => 10,
],
];
}
}