import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { RudderClient } from '../../src/client/RudderClient.js'; import { createFakeTokenStore } from '../helpers/FakeTokenStore.js'; import { createFakeGateway, type FakeGateway } from '../helpers/fakeGateway.js'; import { stubDeterministicUuid } from '../helpers/memoryPlanStore.js'; import { plan } from '../helpers/plan.js'; import type { RemoteConfig } from '../../src/generated/remote-config.js'; interface GameRemoteConfig extends Record { max_energy: number; drop_rate: number; pvp_enabled: boolean; welcome_text: string; boss: { hp: number; name: string }; legacy: string; missing_declared: string; } function cfg(key: string, value: string, valueType: NonNullable, active = true): RemoteConfig { return { key, value, valueType, active }; } function makeClient(): RudderClient { return new RudderClient({ baseUrl: 'https://api.test.rudder.build', projectKey: 'test-project', tokenStore: createFakeTokenStore(), runtime: { planStateStore: null, loginEvent: null }, }); } describe('E2E: remote config', () => { let gateway: FakeGateway; let client: RudderClient; beforeEach(() => { stubDeterministicUuid(); gateway = createFakeGateway({ remoteConfigs: { max_energy: cfg('max_energy', '50', 'int'), drop_rate: cfg('drop_rate', '0.25', 'float'), pvp_enabled: cfg('pvp_enabled', 'true', 'bool'), welcome_text: cfg('welcome_text', 'Hello!', 'string'), boss: cfg('boss', '{"hp":1000,"name":"Drake"}', 'json'), legacy: cfg('legacy', 'old', 'string', false), // inactive — must be excluded }, }); gateway.install(); client = makeClient(); }); afterEach(() => vi.unstubAllGlobals()); it('loads configs and reads them back with type coercion', async () => { await client.auth.loginWithDevice(); expect(client.remoteConfig.status).toBe('ready'); expect(client.remoteConfig.get('max_energy', 0)).toBe(50); expect(client.remoteConfig.get('drop_rate', 0)).toBeCloseTo(0.25); expect(client.remoteConfig.get('pvp_enabled', false)).toBe(true); expect(client.remoteConfig.get('welcome_text', '')).toBe('Hello!'); expect(client.remoteConfig.get('boss', { hp: 0, name: '' })).toEqual({ hp: 1000, name: 'Drake', }); }); it('inactive configs are not exposed; missing keys fall back to default', async () => { await client.auth.loginWithDevice(); expect(client.remoteConfig.get('legacy', 'DEFAULT')).toBe('DEFAULT'); expect(client.remoteConfig.get('missing_declared', 'fallback')).toBe('fallback'); }); it('get() before login returns the default (no throw)', () => { expect(client.remoteConfig.status).toBe('idle'); expect(client.remoteConfig.get('max_energy', 99)).toBe(99); }); it('a remote_config_override scenario node patches the live cache', async () => { client = new RudderClient({ baseUrl: 'https://api.test.rudder.build', projectKey: 'test-project', tokenStore: createFakeTokenStore(), runtime: { planStateStore: null }, }); gateway.onEvent( 'player_login', plan('boost') .node('override', 'remote_config_override', { patches: [{ path: 'drop_rate', valueType: 'float', value: '0.9' }], }) .build(), ); await client.auth.loginWithDevice(); // The override is reflected immediately, without a reload. expect(client.remoteConfig.get('drop_rate', 0)).toBeCloseTo(0.9); }); });