CaiWuXiTong/tests/Unit/Auth/FinanceRosterTest.php

72 lines
4.5 KiB
PHP

<?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
{
#[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'] ?? []]);
$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 [
'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],
];
}
}