351 lines
11 KiB
PHP
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();
|
|
}
|
|
});
|
|
}
|
|
}
|