Files
supabase/packages/common/configcat.test.ts
T
Charis 453c31da16 refactor(common): drop ConfigCat proxy probe in favor of waitForReady (#45939)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Refactor / performance.

## What is the current behavior?

`packages/common/configcat.ts` does a two-step setup: a probe `fetch` to
the ConfigCat proxy URL, followed by SDK client initialization with the
proxy as `baseUrl` (or against the direct ConfigCat CDN if the probe
fails). On the happy path this fires **two** network requests for the
same JSON config on cold start — the probe, then the SDK's own initial
AutoPoll fetch.

## What is the new behavior?

The probe is removed. We initialize the proxy client directly and
inspect the `ClientCacheState` returned by `waitForReady()`. On
`NoFlagData` (proxy unreachable, no cache) we `dispose()` the proxy
client and fall back to the direct SDK key client. Cold-start fetches
drop from 2 to 1 when the proxy is healthy. Worst-case fallback delay is
bounded by `maxInitWaitTimeSeconds` (5s default), comparable to today's
probe timeout on a broken proxy.

The unused exported \`fetchHandler\` is removed (no external importers —
verified via grep). Tests in \`configcat.test.ts\` are updated to mock
\`waitForReady\`/\`dispose\` and a new test covers the proxy-failure
fallback path.

## Additional context

Resolves FE-3174

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Improved ConfigCat client initialization with robust fallback handling
when proxy is unavailable.

* **Chores**
  * Removed `fetchHandler` export from ConfigCat module.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45939)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-14 12:32:58 -04:00

81 lines
2.5 KiB
TypeScript

import * as configcat from 'configcat-js'
import { beforeEach, describe, expect, it, vi } from 'vitest'
vi.mock('configcat-js', () => ({
getClient: vi.fn(),
PollingMode: {
AutoPoll: 'AutoPoll',
},
ClientCacheState: {
NoFlagData: 0,
HasLocalOverrideFlagDataOnly: 1,
HasCachedFlagDataOnly: 2,
HasUpToDateFlagData: 3,
},
User: vi.fn(),
}))
describe('configcat', () => {
const mockClient = {
getAllValuesAsync: vi.fn(),
waitForReady: vi.fn().mockResolvedValue(3), // HasUpToDateFlagData
dispose: vi.fn(),
}
beforeEach(() => {
vi.clearAllMocks()
vi.resetModules()
vi.unstubAllEnvs()
;(configcat.getClient as any).mockReturnValue(mockClient)
})
it('should return empty array when no email is provided', async () => {
const { getFlags } = await import('./configcat')
const result = await getFlags()
expect(result).toEqual([])
})
it('should call getAllValuesAsync with user when email is provided', async () => {
vi.stubEnv('NEXT_PUBLIC_CONFIGCAT_PROXY_URL', 'https://proxy.example.com')
const { getFlags } = await import('./configcat')
const email = 'test@example.com'
const mockValues = { flag1: true, flag2: false }
mockClient.getAllValuesAsync.mockResolvedValue(mockValues)
const result = await getFlags(email)
expect(configcat.User).toHaveBeenCalledWith(email, undefined, undefined, expect.any(Object))
expect(mockClient.getAllValuesAsync).toHaveBeenCalled()
expect(result).toEqual(mockValues)
})
it('should fall back to SDK key client when proxy returns NoFlagData', async () => {
vi.stubEnv('NEXT_PUBLIC_CONFIGCAT_PROXY_URL', 'https://proxy.example.com')
vi.stubEnv('NEXT_PUBLIC_CONFIGCAT_SDK_KEY', 'test-sdk-key')
const proxyClient = {
getAllValuesAsync: vi.fn(),
waitForReady: vi.fn().mockResolvedValue(0), // NoFlagData
dispose: vi.fn(),
}
const sdkClient = {
getAllValuesAsync: vi.fn().mockResolvedValue([]),
waitForReady: vi.fn().mockResolvedValue(3), // HasUpToDateFlagData
dispose: vi.fn(),
}
;(configcat.getClient as any).mockReturnValueOnce(proxyClient).mockReturnValueOnce(sdkClient)
const { getFlags } = await import('./configcat')
await getFlags()
expect(configcat.getClient).toHaveBeenCalledTimes(2)
expect(proxyClient.dispose).toHaveBeenCalled()
expect(configcat.getClient).toHaveBeenNthCalledWith(2, 'test-sdk-key', 'AutoPoll', {
pollIntervalSeconds: 7 * 60,
})
})
})