CaiWuXiTong/tests/Unit/EcommerceFinance/UpstreamBusinessInformationTest.php
DoDo dfc3dcd0d0 fix: 默认隐藏无数据的报表月份列
报表初始只显示有数据的月份列,空列可在筛选字段中手动打开。
2026-10-26 16:32:29 +08:00

286 lines
12 KiB
PHP

<?php
namespace Tests\Unit\EcommerceFinance;
use App\Business\UpstreamBusinessInformation;
use Closure;
use Mockery;
use Modules\EcommerceFinance\Repositories\BusinessInformationMirrorRepository;
use Modules\EcommerceFinance\Repositories\StoreAccountMirrorRepository;
use Modules\EcommerceFinance\Repositories\StoreReportRepository;
use Modules\EcommerceFinance\Services\UpstreamQueryCache;
use Modules\EcommerceFinance\Services\UpstreamQueryCacheFactory;
use Tests\TestCase;
class UpstreamBusinessInformationTest extends TestCase
{
/**
* @dataProvider businessInformationPages
*/
public function test_local_direct_page_resolver_passes_pagination_to_its_fixed_snapshot(
string $section,
string $page,
string $scope,
): void {
config(['upstreams.local_configuration.enabled' => true]);
$cache = new class extends UpstreamQueryCache {
public array $calls = [];
public function __construct()
{
}
public function remember(string $scope, array $parameters, Closure $resolver): mixed
{
$this->calls[] = compact('scope', 'parameters');
return $resolver();
}
};
$caches = new class($cache) extends UpstreamQueryCacheFactory {
public function __construct(private readonly UpstreamQueryCache $cache)
{
}
public function forSource(string $source): UpstreamQueryCache
{
return $this->cache;
}
};
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->once()->with($scope)->andReturnFalse();
$information = new class($caches, $mirror) extends UpstreamBusinessInformation {
public array $snapshots = [];
public function snapshot(string $scope, array $parameters): array
{
$this->snapshots[] = compact('scope', 'parameters');
return ['data' => [], 'total' => 0];
}
};
$this->app['request']->merge(['page' => 3]);
$paginator = $information->paginate($section, $page, 25);
$expected = ['scope' => $scope, 'parameters' => ['page' => 3, 'perPage' => 25]];
$this->assertSame(3, $paginator->currentPage());
$this->assertSame($expected, $cache->calls[0]);
$this->assertSame($expected, $information->snapshots[0]);
}
/**
* @dataProvider paginatedBusinessInformationPages
*/
public function test_ready_business_mirror_is_used_without_registering_a_page_snapshot(
string $section,
string $page,
string $scope,
): void {
config(['operations_replica.enabled' => false, 'upstreams.stored_configuration.enabled' => false]);
$cache = new class extends UpstreamQueryCache {
public function __construct() {}
public function remember(string $scope, array $parameters, Closure $resolver): mixed
{
throw new \RuntimeException('镜像就绪后不应再读取分页快照。');
}
};
$caches = new class($cache) extends UpstreamQueryCacheFactory {
public function __construct(private readonly UpstreamQueryCache $cache) {}
public function forSource(string $source): UpstreamQueryCache { return $this->cache; }
};
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->once()->with($scope)->andReturnTrue();
$mirror->shouldReceive('paginate')->once()->with($scope, 2, 25, [])->andReturn([
'data' => [['id' => 10]],
'total' => 51,
]);
$this->app['request']->merge(['page' => 2]);
$paginator = (new UpstreamBusinessInformation($caches, $mirror))
->paginate($section, $page, 25);
$this->assertSame(2, $paginator->currentPage());
$this->assertSame(51, $paginator->total());
$this->assertSame(10, $paginator->items()[0]->id);
}
public function test_store_basic_information_uses_account_owner_and_department_head(): void
{
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->once()->with('store-information.read')->andReturnTrue();
$mirror->shouldReceive('allStores')->once()->with([])->andReturn([
[
'id' => 7,
'erp_store_name' => '拼多多_测试店铺',
'store_manager' => '旧店铺负责人',
'department_manager' => '旧部门负责人',
'store_status' => '0',
'platform' => '拼多多',
],
[
'id' => 8,
'erp_store_name' => '拼多多-其他店铺',
'store_manager' => '其他负责人',
'department_manager' => '其他部门负责人',
'store_status' => '6',
'platform' => '拼多多',
],
]);
$accounts = Mockery::mock(StoreAccountMirrorRepository::class);
$accounts->shouldReceive('ownershipByStoreName')->once()->andReturn([
StoreAccountMirrorRepository::storeNameKey('拼多多-测试店铺') => [
'operation_owner' => '正确店铺负责人',
'operation_department_head' => '正确部门负责人',
'operation_director' => '运营总监',
'profit_center' => '运营中心A',
],
]);
$this->app['request']->merge([
'store_manager' => '正确店铺负责人',
'department_manager' => '正确部门负责人',
]);
$paginator = (new UpstreamBusinessInformation(
Mockery::mock(UpstreamQueryCacheFactory::class),
$mirror,
null,
$accounts,
))->paginate('store-management', 'basic-information', 50);
$this->assertSame(1, $paginator->total());
$this->assertSame('正确店铺负责人', $paginator->items()[0]->store_manager);
$this->assertSame('正确部门负责人', $paginator->items()[0]->department_manager);
$this->assertNotSame('运营总监', $paginator->items()[0]->department_manager);
$this->assertSame('正常', $paginator->items()[0]->store_status);
$summaryCards = collect($this->app['request']->attributes->get('business-page-summary-cards'))->keyBy('label');
$this->assertSame([], $summaryCards->all());
}
public function test_base_information_pages_do_not_render_summary_cards(): void
{
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->times(2)->andReturnUsing(
static fn (string $scope): bool => in_array($scope, ['company-information.read', 'trademark-information.read'], true),
);
$mirror->shouldReceive('paginate')->once()->with('company-information.read', 1, 50, [])->andReturn([
'data' => [],
'total' => 818,
'summary' => [
'total' => 818,
'normal' => 520,
'deregistered' => 298,
'general_taxpayer' => 123,
'small_taxpayer' => 606,
],
]);
$mirror->shouldReceive('paginate')->once()->with('trademark-information.read', 1, 50, [])->andReturn([
'data' => [],
'total' => 593,
'summary' => [
'total' => 593,
'enabled' => 548,
'disabled' => 45,
'holders' => 5,
'departments' => 6,
],
]);
$information = new UpstreamBusinessInformation(
Mockery::mock(UpstreamQueryCacheFactory::class),
$mirror,
null,
);
$information->paginate('company-management', 'basic-information', 50);
$companyCards = collect($this->app['request']->attributes->get('business-page-summary-cards'))->keyBy('label');
$this->assertSame([], $companyCards->all());
$information->paginate('trademark-management', 'information', 50);
$trademarkCards = collect($this->app['request']->attributes->get('business-page-summary-cards'))->keyBy('label');
$this->assertSame([], $trademarkCards->all());
}
public function test_bootstrap_without_mirror_explains_shared_connection_instead_of_requesting_upstream_credentials(): void
{
config(['system_database.mode' => 'bootstrap', 'upstreams.local_configuration.enabled' => false]);
$caches = Mockery::mock(UpstreamQueryCacheFactory::class);
$caches->shouldNotReceive('forSource');
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->once()->with('store-information.read')->andReturnFalse();
$this->expectException(\Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class);
$this->expectExceptionMessage('无需另配上游 SQL Server');
(new UpstreamBusinessInformation($caches, $mirror))
->paginate('store-management', 'basic-information');
}
public function test_subject_operating_report_hides_income_months_without_data_by_default(): void
{
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->with('store-information.read')->andReturnTrue();
$mirror->shouldReceive('allStores')->andReturn([['erp_store_name' => '测试店铺']]);
$mirror->shouldReceive('ready')->with('company-information.read')->andReturnTrue();
$mirror->shouldReceive('companiesByNames')->andReturn([]);
$mirror->shouldReceive('companyClassificationsByNames')->andReturn([]);
$storeReports = Mockery::mock(StoreReportRepository::class);
$storeReports->shouldReceive('rows')->andReturn([[
'entity' => '测试主体',
'company_type' => '店铺-普通公司',
'rolling_12m_income' => 188.5,
'income_01' => null,
'income_02' => 88.5,
'income_03' => 0.0,
'income_04' => null,
'income_05' => null,
'income_06' => null,
'income_07' => null,
'income_08' => null,
'income_09' => null,
'income_10' => null,
'income_11' => null,
'income_12' => null,
]]);
$storeAccounts = Mockery::mock(StoreAccountMirrorRepository::class);
$storeAccounts->shouldReceive('ownershipByStoreName')->andReturn([]);
$information = new UpstreamBusinessInformation(
Mockery::mock(UpstreamQueryCacheFactory::class),
$mirror,
$storeReports,
$storeAccounts,
);
$paginator = $information->paginate('company-management', 'subject-operating-report', 50);
$this->assertSame(
['income_01', 'income_04', 'income_05', 'income_06', 'income_07', 'income_08', 'income_09', 'income_10', 'income_11', 'income_12'],
$this->app['request']->attributes->get('subject-operating-report.default_hidden_columns'),
);
$this->assertSame(1, $paginator->total());
$this->assertSame('测试主体', $paginator->items()[0]->entity);
}
public static function businessInformationPages(): array
{
return [
'company' => ['company-management', 'basic-information', 'company-information.read'],
'store' => ['store-management', 'basic-information', 'store-information.read'],
'trademark' => ['trademark-management', 'information', 'trademark-information.read'],
];
}
public static function paginatedBusinessInformationPages(): array
{
return [
'company' => ['company-management', 'basic-information', 'company-information.read'],
'trademark' => ['trademark-management', 'information', 'trademark-information.read'],
];
}
}