CaiWuXiTong/tests/Unit/EcommerceFinance/UpstreamBusinessInformationTest.php

401 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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_trademark_classification_is_normalized_to_digits_only(): void
{
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->once()->with('trademark-information.read')->andReturnTrue();
$mirror->shouldReceive('paginate')->once()->with('trademark-information.read', 1, 50, [])->andReturn([
'data' => [
['id' => 1, 'trademark_type' => '第20'],
['id' => 2, 'trademark_type' => '第20类;第35类'],
['id' => 3, 'trademark_type' => null],
],
'total' => 3,
]);
$paginator = (new UpstreamBusinessInformation(
Mockery::mock(UpstreamQueryCacheFactory::class),
$mirror,
null,
))->paginate('trademark-management', 'information', 50);
$this->assertSame('20', $paginator->items()[0]->trademark_type);
$this->assertSame('20,35', $paginator->items()[1]->trademark_type);
$this->assertNull($paginator->items()[2]->trademark_type);
}
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_company_registration_date_is_hidden_for_deregistered_and_invalid_pre_2000_values(): void
{
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->once()->with('company-information.read')->andReturnTrue();
$mirror->shouldReceive('paginate')->once()->with('company-information.read', 1, 50, [])->andReturn([
'data' => [
['id' => 1, 'company_status' => '1', 'establishment_date' => '2024-04-28'],
['id' => 2, 'company_status' => '已注销', 'establishment_date' => null],
['id' => 3, 'company_status' => '0', 'establishment_date' => '1900-01-01'],
['id' => 4, 'company_status' => '正常', 'establishment_date' => '1999-12-31'],
['id' => 5, 'company_status' => '0', 'establishment_date' => '2000-01-01'],
['id' => 6, 'company_status' => null, 'establishment_date' => null],
],
'total' => 6,
]);
$items = (new UpstreamBusinessInformation(
Mockery::mock(UpstreamQueryCacheFactory::class),
$mirror,
null,
))->paginate('company-management', 'basic-information', 50)->items();
$this->assertSame('已注销', $items[0]->company_status);
$this->assertSame('/', $items[0]->establishment_date);
$this->assertSame('/', $items[1]->establishment_date);
$this->assertSame('/', $items[2]->establishment_date);
$this->assertSame('/', $items[3]->establishment_date);
$this->assertSame('2000-01-01', $items[4]->establishment_date);
$this->assertNull($items[5]->establishment_date);
}
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 function test_subject_status_counts_use_all_rows_in_the_current_filter_result(): void
{
$mirror = Mockery::mock(BusinessInformationMirrorRepository::class);
$mirror->shouldReceive('ready')->with('store-information.read')->andReturnTrue();
$mirror->shouldReceive('allStores')->once()->andReturn([['erp_store_name' => '测试店铺']]);
$mirror->shouldReceive('ready')->with('company-information.read')->andReturnTrue();
$mirror->shouldReceive('companiesByNames')->once()->andReturn([]);
$mirror->shouldReceive('companyClassificationsByNames')->once()->andReturn([
'主体甲' => [
'status' => '保留',
'tax_type' => '小规模纳税人',
'business_registration_type' => '个人独资企业',
],
'主体乙' => [
'status' => '待注销',
'tax_type' => '一般纳税人',
'business_registration_type' => '核定',
],
]);
$storeReports = Mockery::mock(StoreReportRepository::class);
$storeReports->shouldReceive('rows')->once()->andReturn([
[
'entity' => '主体甲',
'company_type' => '店铺-普通公司',
'rolling_12m_income' => 100,
],
[
'entity' => '主体乙',
'company_type' => '店铺-国标公司',
'rolling_12m_income' => 200,
],
]);
$storeAccounts = Mockery::mock(StoreAccountMirrorRepository::class);
$storeAccounts->shouldReceive('ownershipByStoreName')->once()->andReturn([]);
$information = new UpstreamBusinessInformation(
Mockery::mock(UpstreamQueryCacheFactory::class),
$mirror,
$storeReports,
$storeAccounts,
);
$paginator = $information->paginate('company-management', 'subject-operating-report', 1);
$counts = $this->app['request']->attributes->get('directory.status-counts');
$fields = collect($counts['fields'])->keyBy('name');
$valueCounts = static fn (string $field): array => collect($fields[$field]['values'])->pluck('count', 'value')->all();
$this->assertSame(2, $paginator->total());
$this->assertCount(1, $paginator->items());
$this->assertSame(2, $counts['total']);
$this->assertSame(1, $valueCounts('company_status')['正常']);
$this->assertSame(1, $valueCounts('company_status')['已注销']);
$this->assertSame(1, $valueCounts('company_type')['店铺-普通公司']);
$this->assertSame(1, $valueCounts('company_type')['店铺-国标公司']);
$this->assertSame(1, $valueCounts('tax_type')['小规模纳税人']);
$this->assertSame(1, $valueCounts('tax_type')['一般纳税人']);
$this->assertSame(2, $valueCounts('plan')['']);
}
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'],
];
}
}