CaiWuXiTong/tests/Unit/Auth/FinanceRosterTest.php
limeng f41a8fa6ba 钉钉登录:单独授权人员仅访问三个快递报价页面 (#339)
按精确企业身份开放受限登录,保留在职校验和普通角色;统一限制菜单、页面、导出与预览。59 项相关测试通过,无数据库迁移。
2026-10-15 15:21:05 +08:00

95 lines
6.7 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\Auth;
use App\Services\Auth\FinanceRoster;
use Mockery;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQuery;
use Modules\EcommerceFinance\Services\UpstreamReadOnlyQueryFactory;
use PHPUnit\Framework\Attributes\DataProvider;
use Tests\TestCase;
class FinanceRosterTest extends TestCase
{
public function test_directory_search_is_bounded_parameterized_and_escapes_wildcards(): void
{
$query = Mockery::mock(UpstreamReadOnlyQuery::class);
$query->shouldReceive('select')->once()->with(Mockery::on(function ($sql): bool {
UpstreamReadOnlyQuery::assertReadOnlySql($sql);
return str_contains($sql, 'TOP (12)') && str_contains($sql, 'flowerNameId') && ! str_contains($sql, "张'_%");
}), ["%张'!_!%![%", "%张'!_!%![%"])->andReturn([]);
$factory = Mockery::mock(UpstreamReadOnlyQueryFactory::class);
$factory->shouldReceive('forSource')->once()->with('operations_replica')->andReturn($query);
$roster = new FinanceRoster($factory);
$this->assertSame([], $roster->search(''));
$this->assertSame([], $roster->search("张'_%["));
}
#[DataProvider('rosterCases')]
public function test_roster_authorization(array $overrides, bool $expected): void
{
config(['dingtalk.finance_department_ids' => ['22848291', '994080505']]);
config(['dingtalk.super_admin_user_ids' => $overrides['designated_ids'] ?? []]);
config(['dingtalk.limited_login_user_ids' => $overrides['limited_ids'] ?? []]);
$data = array_replace([
'flowers' => [['Id' => 123, 'name' => '测试员工', 'flowerName' => '测试花名', 'deptId' => '55248182', 'status' => '', 'empStatus' => '正式']],
'employees' => [['active' => 1, 'dept_id_list' => '[55248182]']],
'registrations' => [['employment_status' => '3', 'department_ids' => '55248182', 'main_department_id' => '55248182']],
'resignation' => null,
'ancestors' => [['Id' => '55248182', 'parent_id' => '22848291', 'depth' => 0], ['Id' => '22848291', 'parent_id' => '1', 'depth' => 1]],
], $overrides);
$query = Mockery::mock(UpstreamReadOnlyQuery::class);
$query->shouldReceive('select')->andReturnUsing(function ($sql, $bindings) use ($data) {
UpstreamReadOnlyQuery::assertReadOnlySql($sql);
if (str_contains($sql, 'WITH lineage')) {
$this->assertContains('55248182', $bindings);
return $data['ancestors'];
}
$this->assertSame(['ding-fixture-id'], $bindings);
return match (true) {
str_contains($sql, 'hr_flowerName_book') => $data['flowers'],
str_contains($sql, 'hr_employee_register') => $data['registrations'],
str_contains($sql, 'hr_employee') => $data['employees'],
};
});
$query->shouldReceive('first')->with(Mockery::on(function ($sql) {
UpstreamReadOnlyQuery::assertReadOnlySql($sql);
return str_contains($sql, 'hr_employee_dimission');
}), ['ding-fixture-id'])->andReturn($data['resignation']);
$factory = Mockery::mock(UpstreamReadOnlyQueryFactory::class);
$factory->shouldReceive('forSource')->with('operations_replica')->andReturn($query);
$result = (new FinanceRoster($factory))->findEligible('ding-fixture-id');
$this->assertSame($expected, $result !== null);
}
public static function rosterCases(): array
{
return [
'limited current employee outside finance' => [['limited_ids' => ['ding-fixture-id'], 'ancestors' => [['Id' => '55248182', 'parent_id' => '1', 'depth' => 0]]], true],
'limited grant is exact identity only' => [['limited_ids' => ['another-id'], 'ancestors' => [['Id' => '55248182', 'parent_id' => '1', 'depth' => 0]]], false],
'limited grant still requires unique roster identity' => [['limited_ids' => ['ding-fixture-id'], 'flowers' => []], false],
'limited grant rejects duplicate roster identity' => [['limited_ids' => ['ding-fixture-id'], 'flowers' => [['Id' => 1], ['Id' => 2]]], false],
'limited grant cannot bypass resignation' => [['limited_ids' => ['ding-fixture-id'], 'resignation' => ['userid' => 'ding-fixture-id']], false],
'limited grant cannot bypass disabled employment' => [['limited_ids' => ['ding-fixture-id'], 'employees' => [['active' => 0]]], false],
'limited grant cannot bypass resigned registration' => [['limited_ids' => ['ding-fixture-id'], 'registrations' => [['employment_status' => '5']]], false],
'limited grant cannot bypass broken organization' => [['limited_ids' => ['ding-fixture-id'], 'ancestors' => [['Id' => '55248182', 'parent_id' => '55248182', 'depth' => 20]]], false],
'designated active administrator outside finance' => [['designated_ids' => ['ding-fixture-id'], 'ancestors' => [['Id' => '55248182', 'parent_id' => '1', 'depth' => 0]]], true],
'designation does not cover another identity' => [['designated_ids' => ['another-id'], 'ancestors' => [['Id' => '55248182', 'parent_id' => '1', 'depth' => 0]]], false],
'designated administrator still requires active employment' => [['designated_ids' => ['ding-fixture-id'], 'employees' => [['active' => 0]]], false],
'designated administrator cannot bypass resignation' => [['designated_ids' => ['ding-fixture-id'], 'resignation' => ['userid' => 'ding-fixture-id']], false],
'designated administrator needs roster identity' => [['designated_ids' => ['ding-fixture-id'], 'flowers' => []], false],
'finance descendant' => [[], true],
'second finance root' => [['ancestors' => [['Id' => '994080505', 'parent_id' => '994121400', 'depth' => 1]]], true],
'concurrent finance membership' => [['employees' => [['active' => 1, 'dept_id_list' => '[100,55248182]']]], true],
'not in roster' => [['flowers' => []], false],
'same department name outside allowed roots' => [['ancestors' => [['Id' => '123456', 'parent_id' => '1', 'depth' => 0]]], false],
'resignation beats active status' => [['resignation' => ['userid' => 'ding-fixture-id']], false],
'numeric resigned status' => [['registrations' => [['employment_status' => '5']]], false],
'disabled employee' => [['employees' => [['active' => 0]]], false],
'flower-only member' => [['employees' => [], 'registrations' => []], true],
'ambiguous roster identity' => [['flowers' => [['Id' => 1], ['Id' => 2]]], false],
'broken department cycle' => [['ancestors' => [['Id' => '22848291', 'parent_id' => '22848291', 'depth' => 20]]], false],
];
}
}