Files
datahub/backend/test/Cases/Unit/Service/ScopeBitmapServiceTest.php
T
2026-03-09 14:15:44 +08:00

351 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace HyperfTest\Cases\Unit\Service;
use App\Model\Platform;
use App\Model\Store;
use App\Model\User;
use App\Model\UserDataScope;
use App\Service\ScopeBitmapService;
use Hyperf\DbConnection\Db;
use PHPUnit\Framework\TestCase;
/**
* @internal
* @coversNothing
*/
class ScopeBitmapServiceTest extends TestCase
{
protected ScopeBitmapService $service;
protected function setUp(): void
{
parent::setUp();
$this->service = new ScopeBitmapService();
}
protected function runInCoroutine(callable $callback): void
{
if (\Swoole\Coroutine::getCid() > 0) {
$callback();
return;
}
$exception = null;
\Swoole\Coroutine\run(static function () use ($callback, &$exception): void {
try {
$callback();
} catch (\Throwable $e) {
$exception = $e;
}
});
if ($exception) {
throw $exception;
}
}
// --- encode/decode 双向转换 ---
public function test_encode_empty_array_returns_empty_string(): void
{
$this->assertSame('', $this->service->encode([]));
}
public function test_encode_single_store_id(): void
{
$bitmap = $this->service->encode([1]);
$this->assertSame([1], $this->service->decode($bitmap));
}
public function test_encode_multiple_store_ids(): void
{
$ids = [1, 3, 5, 8, 10];
$bitmap = $this->service->encode($ids);
$decoded = $this->service->decode($bitmap);
$this->assertSame($ids, $decoded);
}
public function test_encode_decode_large_ids(): void
{
$ids = [1, 100, 500, 1000];
$bitmap = $this->service->encode($ids);
$decoded = $this->service->decode($bitmap);
$this->assertSame($ids, $decoded);
}
public function test_decode_empty_string_returns_empty_array(): void
{
$this->assertSame([], $this->service->decode(''));
}
public function test_encode_preserves_order(): void
{
// 编码时乱序,解码后应升序
$bitmap = $this->service->encode([5, 1, 3]);
$this->assertSame([1, 3, 5], $this->service->decode($bitmap));
}
// --- has 检查 ---
public function test_has_returns_true_for_existing_id(): void
{
$bitmap = $this->service->encode([1, 5, 10]);
$this->assertTrue($this->service->has($bitmap, 1));
$this->assertTrue($this->service->has($bitmap, 5));
$this->assertTrue($this->service->has($bitmap, 10));
}
public function test_has_returns_false_for_missing_id(): void
{
$bitmap = $this->service->encode([1, 5, 10]);
$this->assertFalse($this->service->has($bitmap, 2));
$this->assertFalse($this->service->has($bitmap, 6));
$this->assertFalse($this->service->has($bitmap, 11));
}
public function test_has_returns_false_for_id_beyond_bitmap_length(): void
{
$bitmap = $this->service->encode([1]);
$this->assertFalse($this->service->has($bitmap, 100));
}
public function test_has_returns_false_for_empty_bitmap(): void
{
$this->assertFalse($this->service->has('', 1));
}
// --- merge 合并 ---
public function test_merge_two_bitmaps(): void
{
$bitmap1 = $this->service->encode([1, 3]);
$bitmap2 = $this->service->encode([2, 4]);
$merged = $this->service->merge($bitmap1, $bitmap2);
$this->assertSame([1, 2, 3, 4], $this->service->decode($merged));
}
public function test_merge_overlapping_bitmaps(): void
{
$bitmap1 = $this->service->encode([1, 3, 5]);
$bitmap2 = $this->service->encode([3, 5, 7]);
$merged = $this->service->merge($bitmap1, $bitmap2);
$this->assertSame([1, 3, 5, 7], $this->service->decode($merged));
}
public function test_merge_different_length_bitmaps(): void
{
$bitmap1 = $this->service->encode([1]);
$bitmap2 = $this->service->encode([100]);
$merged = $this->service->merge($bitmap1, $bitmap2);
$this->assertTrue($this->service->has($merged, 1));
$this->assertTrue($this->service->has($merged, 100));
}
public function test_merge_with_empty_bitmaps(): void
{
$bitmap = $this->service->encode([1, 3]);
$merged = $this->service->merge($bitmap, '');
$this->assertSame([1, 3], $this->service->decode($merged));
}
public function test_merge_all_empty_returns_empty(): void
{
$this->assertSame('', $this->service->merge('', ''));
}
// --- buildForUser(需要数据库) ---
public function test_build_for_user_with_store_scope(): void
{
$this->runInCoroutine(function (): void {
Db::beginTransaction();
try {
$user = User::query()->first();
// 查已有 store
$stores = Store::query()->limit(2)->pluck('id')->toArray();
if (count($stores) < 2) {
$this->markTestSkipped('需要至少 2 个 store 记录');
}
// 插入 store 类型的 scope
foreach ($stores as $store_id) {
UserDataScope::query()->create([
'user_id' => $user->id,
'scope_type' => 'store',
'scope_id' => $store_id,
]);
}
$bitmap = $this->service->buildForUser($user->id);
$this->assertNotEmpty($bitmap);
foreach ($stores as $store_id) {
$this->assertTrue($this->service->has($bitmap, $store_id));
}
} finally {
Db::rollBack();
}
});
}
public function test_build_for_user_with_company_scope(): void
{
$this->runInCoroutine(function (): void {
Db::beginTransaction();
try {
$user = User::query()->first();
// 查找有 store 的 company
$store = Store::query()->first();
if (!$store) {
$this->markTestSkipped('需要至少 1 个 store 记录');
}
UserDataScope::query()->create([
'user_id' => $user->id,
'scope_type' => 'company',
'scope_id' => $store->company_id,
]);
$bitmap = $this->service->buildForUser($user->id);
$this->assertNotEmpty($bitmap);
// 该 company 下的 store 应在 bitmap 中
$company_stores = Store::query()
->where('company_id', $store->company_id)
->pluck('id')
->toArray();
foreach ($company_stores as $sid) {
$this->assertTrue($this->service->has($bitmap, $sid));
}
} finally {
Db::rollBack();
}
});
}
public function test_build_for_user_with_no_scopes_returns_empty(): void
{
$this->runInCoroutine(function (): void {
Db::beginTransaction();
try {
$user = User::query()->first();
// 不插入 scope
$bitmap = $this->service->buildForUser($user->id);
$this->assertSame('', $bitmap);
} finally {
Db::rollBack();
}
});
}
public function test_build_for_user_with_mixed_scopes(): void
{
$this->runInCoroutine(function (): void {
Db::beginTransaction();
try {
$user = User::query()->first();
$store = Store::query()->first();
if (!$store) {
$this->markTestSkipped('需要至少 1 个 store 记录');
}
// 同时添加 company 和 store 类型
UserDataScope::query()->create([
'user_id' => $user->id,
'scope_type' => 'company',
'scope_id' => $store->company_id,
]);
// 找一个不属于该 company 的 store
$other_store = Store::query()
->where('company_id', '!=', $store->company_id)
->first();
if ($other_store) {
UserDataScope::query()->create([
'user_id' => $user->id,
'scope_type' => 'store',
'scope_id' => $other_store->id,
]);
}
$bitmap = $this->service->buildForUser($user->id);
$this->assertNotEmpty($bitmap);
// company 下的 store 在 bitmap 中
$this->assertTrue($this->service->has($bitmap, $store->id));
// 额外的 store 也在
if ($other_store) {
$this->assertTrue($this->service->has($bitmap, $other_store->id));
}
} finally {
Db::rollBack();
}
});
}
// --- buildForDeveloper ---
public function test_build_for_developer_returns_platform_stores(): void
{
$this->runInCoroutine(function (): void {
Db::beginTransaction();
try {
$user = User::query()->first();
// 查找有 store 的 platform
$store = Store::query()->first();
if (!$store) {
$this->markTestSkipped('需要至少 1 个 store 记录');
}
// 将 platform 绑定到用户
Platform::query()
->where('id', $store->platform_id)
->update(['developer_id' => $user->id]);
$bitmap = $this->service->buildForDeveloper($user->id);
$this->assertNotEmpty($bitmap);
$platform_stores = Store::query()
->where('platform_id', $store->platform_id)
->pluck('id')
->toArray();
foreach ($platform_stores as $sid) {
$this->assertTrue($this->service->has($bitmap, $sid));
}
} finally {
Db::rollBack();
}
});
}
public function test_build_for_developer_no_platforms_returns_empty(): void
{
$this->runInCoroutine(function (): void {
Db::beginTransaction();
try {
// 创建一个不维护任何平台的用户
$user = User::query()->create([
'username' => 'dev_no_platform_' . uniqid(),
'email' => 'devnp_' . uniqid() . '@test.com',
'password' => 'password',
'status' => 1,
]);
$bitmap = $this->service->buildForDeveloper($user->id);
$this->assertSame('', $bitmap);
} finally {
Db::rollBack();
}
});
}
}