set('upstreams.stored_configuration.enabled', false); config()->set('upstreams.local_configuration.enabled', true); config()->set('upstreams.snapshots.cache_store', 'upstream_shared'); config()->set('upstreams.snapshots.allow_inline_refresh', true); config()->set('upstreams.runtime.max_concurrency', 4); config()->set('upstreams.runtime.slot_wait_ms', 0); config()->set('upstreams.sources.shared_source', [ 'label' => '共享来源', 'config' => 'test_shared_source', ]); config()->set('test_shared_source', [ 'enabled' => true, 'allowed_scopes' => ['orders.*'], 'connection' => [ 'connect_timeout' => 1, 'query_timeout' => 1, ], 'snapshots' => [ 'ttl_seconds' => 60, 'summary_ttl_seconds' => 60, 'options_ttl_seconds' => 3600, 'stale_ttl_seconds' => 3600, 'active_ttl_seconds' => 3600, 'registry_limit' => 50, 'refresh_limit' => 2, 'miss_budget_per_window' => 6, 'budget_window_seconds' => 60, 'lock_seconds' => 15, 'lock_wait_seconds' => 1, 'max_concurrency' => 2, 'slot_wait_ms' => 0, 'failure_cooldown_seconds' => 60, 'query_failure_cooldown_seconds' => 60, 'slow_query_ms' => 5000, 'slow_query_cooldown_seconds' => 60, 'refresh_time_budget_seconds' => 10, 'refresh_pause_ms' => 0, ], ]); } public function test_two_runtime_instances_share_one_database_snapshot_without_copying_source_tables(): void { $sources = app(UpstreamSourceRegistry::class); $cacheManager = app(CacheManager::class); $firstRuntime = new UpstreamQueryCache($sources, 'shared_source', $cacheManager); $secondRuntime = new UpstreamQueryCache($sources, 'shared_source', $cacheManager); $calls = 0; $first = $firstRuntime->remember('orders.list', ['month' => '2026-07'], function () use (&$calls): array { $calls++; return ['rows' => [['id' => 1]], 'total' => 1]; }); $second = $secondRuntime->remember('orders.list', ['month' => '2026-07'], function () use (&$calls): array { $calls++; return ['rows' => [['id' => 999]], 'total' => 999]; }); $this->assertSame($first, $second); $this->assertSame(1, $calls); $this->assertGreaterThan(0, DB::table('upstream_query_cache')->count()); $this->assertTrue(Schema::hasTable('upstream_query_cache')); $this->assertTrue(Schema::hasTable('upstream_query_locks')); $this->assertFalse(Schema::hasTable('orders')); } public function test_refresh_all_safely_skips_a_registered_but_disabled_source(): void { config()->set('jiansun.enabled', false); $this->artisan('upstream:snapshots:refresh-all') ->expectsOutputToContain('同步未启用,本轮跳过') ->assertExitCode(0); } public function test_permission_change_creates_a_separate_snapshot_scope(): void { $user = user_model_class()::firstOrFail(); $user->companies()->firstOrFail()->makeCurrent(); $this->actingAs($user); $sources = app(UpstreamSourceRegistry::class); $cacheManager = app(CacheManager::class); $calls = 0; $before = (new UpstreamQueryCache($sources, 'shared_source', $cacheManager)) ->remember('orders.list', [], function () use (&$calls): array { $calls++; return ['permission_version' => 1]; }); $permission = Permission::query()->create([ 'name' => 'read-shared-snapshot-test', 'display_name' => 'Read Shared Snapshot Test', 'description' => '测试共享快照权限隔离', ]); $user->attachPermission($permission); $after = (new UpstreamQueryCache($sources, 'shared_source', $cacheManager)) ->remember('orders.list', [], function () use (&$calls): array { $calls++; return ['permission_version' => 2]; }); $this->assertSame(1, $before['permission_version']); $this->assertSame(2, $after['permission_version']); $this->assertSame(2, $calls); } }