mirror of
https://github.com/supabase/supabase.git
synced 2026-10-08 19:05:06 +03:00
## Context PR here mainly breaks up the files under `ConnectSheet` to separate the functional logic so that we can write unit tests. No behavior changes intended beyond the bug fixes ## Changes involved - **Test organization:** moved all root-level `ConnectSheet` test files into `ConnectSheet/__tests__/` for consistency with other parts of the codebase that use this convention. - **Bug fix:** read replica label had a stray `}` / missing `)`, rendering as e.g. `Read Replica (us-east-1 - abc123})` instead of `Read Replica (us-east-1 - abc123)`. - **`ConnectSheet.tsx`:** extracted the "hydrate sheet state on open" `useEffect` logic (mode/field/URL param resolution from URL vs. localStorage) into a new `ConnectSheet.utils.ts`, with unit tests - **`useConnectServerEnv.ts`:** fixed two race conditions in the secret reveal/hide flow: - `toggle()` and `getValue()` could each fire a separate reveal request if triggered close together — now deduped to share one in-flight request. - `getValue()` could hide a secret that had just been explicitly revealed by a concurrent `toggle()`, due to reading a stale closure value — now reads the live state via `useLatest`. - Also stopped swallowing the original error on reveal failure (now attached via `cause`). - Added tests for the above, plus the 10s auto-hide timer (previously untested). - **`ConnectStepsSection.tsx`:** extracted `resolveContentPath` and the three inline "show notice" booleans (IPv4 addon, session pooler, self-hosted MCP) into `ConnectStepsSection.utils.ts`, matching the existing pattern for the Data API notice. Added unit tests for all of them. ## To test - [ ] Just a basic smoke test of the Connect sheet should do <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Improved connect setup hydration so saved preferences and URL values are applied more consistently when opening the sheet, including automatic URL backfilling where needed. * Refreshed connection guidance notices (IPv4 add-on, session pooler, and self-hosted MCP) with more consistent logic. * **Bug Fixes** * Fixed secret reveal behavior to keep concurrent reveal actions in sync, handle failures more safely, and ensure auto-hide works reliably. * Corrected the read-replica option label formatting. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
179 lines
5.6 KiB
TypeScript
179 lines
5.6 KiB
TypeScript
import { describe, expect, test } from 'vitest'
|
|
|
|
import {
|
|
buildConnectionParameters,
|
|
buildConnectionStringWithPassword,
|
|
buildSafeConnectionString,
|
|
DEFAULT_PORT,
|
|
parseConnectionParams,
|
|
PASSWORD_PLACEHOLDER,
|
|
resolveConnectionString,
|
|
} from '../ConnectionString.utils'
|
|
|
|
describe('parseConnectionParams', () => {
|
|
test('returns hidden defaults for an empty string', () => {
|
|
expect(parseConnectionParams('')).toEqual({
|
|
host: 'hidden',
|
|
port: DEFAULT_PORT,
|
|
user: 'hidden',
|
|
database: 'hidden',
|
|
})
|
|
})
|
|
|
|
test('returns hidden defaults for an unparseable URL', () => {
|
|
expect(parseConnectionParams('not a url')).toEqual({
|
|
host: 'hidden',
|
|
port: DEFAULT_PORT,
|
|
user: 'hidden',
|
|
database: 'hidden',
|
|
})
|
|
})
|
|
|
|
test('parses a platform-shaped connection string', () => {
|
|
const uri =
|
|
'postgresql://postgres.projref:[YOUR-PASSWORD]@aws-0-eu-west-1.pooler.supabase.com:6543/postgres'
|
|
expect(parseConnectionParams(uri)).toEqual({
|
|
host: 'aws-0-eu-west-1.pooler.supabase.com',
|
|
port: '6543',
|
|
user: 'postgres.projref',
|
|
database: 'postgres',
|
|
})
|
|
})
|
|
|
|
test('decodes percent-encoded bracket placeholders in the user info', () => {
|
|
// The URL parser percent-encodes the `[`/`]` in self-hosted's POOLER_TENANT_ID placeholder.
|
|
// parseConnectionParams must decode so the displayed user matches what we wrote.
|
|
const uri =
|
|
'postgresql://postgres.[POOLER_TENANT_ID]:[YOUR-PASSWORD]@supabase.example.com:6543/postgres'
|
|
expect(parseConnectionParams(uri).user).toBe('postgres.[POOLER_TENANT_ID]')
|
|
})
|
|
})
|
|
|
|
describe('buildSafeConnectionString', () => {
|
|
test('returns empty string when input is empty', () => {
|
|
expect(buildSafeConnectionString('', parseConnectionParams(''))).toBe('')
|
|
})
|
|
|
|
test('rebuilds the URL with PASSWORD_PLACEHOLDER and the parsed params (round-trips brackets)', () => {
|
|
const uri =
|
|
'postgresql://postgres.[POOLER_TENANT_ID]:[YOUR-PASSWORD]@supabase.example.com:6543/postgres'
|
|
const params = parseConnectionParams(uri)
|
|
const safe = buildSafeConnectionString(uri, params)
|
|
expect(safe).toBe(
|
|
`postgresql://postgres.[POOLER_TENANT_ID]:${PASSWORD_PLACEHOLDER}@supabase.example.com:6543/postgres`
|
|
)
|
|
})
|
|
|
|
test('preserves search params from the original URL', () => {
|
|
const uri = 'postgresql://postgres.proj:[YOUR-PASSWORD]@host:5432/postgres?sslmode=require'
|
|
const params = parseConnectionParams(uri)
|
|
expect(buildSafeConnectionString(uri, params)).toContain('?sslmode=require')
|
|
})
|
|
})
|
|
|
|
describe('buildConnectionStringWithPassword', () => {
|
|
test('returns the original string when input or password is empty', () => {
|
|
const uri = `postgresql://postgres:${PASSWORD_PLACEHOLDER}@localhost:5432/postgres`
|
|
|
|
expect(buildConnectionStringWithPassword('', 'password')).toBe('')
|
|
expect(buildConnectionStringWithPassword(uri, '')).toBe(uri)
|
|
})
|
|
|
|
test('replaces every password placeholder with the encoded password', () => {
|
|
const uri = `postgresql://postgres:${PASSWORD_PLACEHOLDER}@localhost:5432/postgres?password=${PASSWORD_PLACEHOLDER}`
|
|
|
|
expect(buildConnectionStringWithPassword(uri, 'p@ss/word#1')).toBe(
|
|
'postgresql://postgres:p%40ss%2Fword%231@localhost:5432/postgres?password=p%40ss%2Fword%231'
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('resolveConnectionString', () => {
|
|
const pooler = {
|
|
transactionShared: 'tx-shared',
|
|
sessionShared: 'session-shared',
|
|
transactionDedicated: 'tx-dedicated',
|
|
sessionDedicated: 'session-dedicated',
|
|
ipv4SupportedForDedicatedPooler: true,
|
|
direct: 'direct-uri',
|
|
}
|
|
|
|
test('returns empty string when pooler bag is undefined', () => {
|
|
expect(
|
|
resolveConnectionString({
|
|
connectionMethod: 'direct',
|
|
useSharedPooler: false,
|
|
connectionStringPooler: undefined,
|
|
})
|
|
).toBe('')
|
|
})
|
|
|
|
test('direct method returns the direct URI', () => {
|
|
expect(
|
|
resolveConnectionString({
|
|
connectionMethod: 'direct',
|
|
useSharedPooler: false,
|
|
connectionStringPooler: pooler,
|
|
})
|
|
).toBe('direct-uri')
|
|
})
|
|
|
|
test('session method returns sessionShared', () => {
|
|
expect(
|
|
resolveConnectionString({
|
|
connectionMethod: 'session',
|
|
useSharedPooler: false,
|
|
connectionStringPooler: pooler,
|
|
})
|
|
).toBe('session-shared')
|
|
})
|
|
|
|
test('transaction prefers dedicated when available and useSharedPooler is false', () => {
|
|
expect(
|
|
resolveConnectionString({
|
|
connectionMethod: 'transaction',
|
|
useSharedPooler: false,
|
|
connectionStringPooler: pooler,
|
|
})
|
|
).toBe('tx-dedicated')
|
|
})
|
|
|
|
test('transaction falls back to shared when useSharedPooler is true', () => {
|
|
expect(
|
|
resolveConnectionString({
|
|
connectionMethod: 'transaction',
|
|
useSharedPooler: true,
|
|
connectionStringPooler: pooler,
|
|
})
|
|
).toBe('tx-shared')
|
|
})
|
|
|
|
test('transaction falls back to shared when no dedicated pooler exists', () => {
|
|
expect(
|
|
resolveConnectionString({
|
|
connectionMethod: 'transaction',
|
|
useSharedPooler: false,
|
|
connectionStringPooler: { ...pooler, transactionDedicated: undefined },
|
|
})
|
|
).toBe('tx-shared')
|
|
})
|
|
})
|
|
|
|
describe('buildConnectionParameters', () => {
|
|
test('produces host/port/database/user rows in display order', () => {
|
|
expect(
|
|
buildConnectionParameters({
|
|
host: 'h',
|
|
port: '5432',
|
|
user: 'u',
|
|
database: 'd',
|
|
})
|
|
).toEqual([
|
|
{ key: 'host', value: 'h' },
|
|
{ key: 'port', value: '5432' },
|
|
{ key: 'database', value: 'd' },
|
|
{ key: 'user', value: 'u' },
|
|
])
|
|
})
|
|
})
|