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(); } }); } }