mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
fix(studio): validate auth conn percentage input between 1 and 100 (#45061)
## Summary - Closes [FE-3039](https://linear.app/supabase/issue/FE-3039/validate-conn-percentage-input-between-1-and-100). - Adds a `superRefine`d `DatabaseFormSchema` in `PerformanceSettingsForm.tsx` that blocks submission when `DB_MAX_POOL_SIZE_UNIT === 'percent'` and the value falls outside 1-100. Connections (fixed pool size) path keeps the existing `min(1)` behavior per the ticket. - Updates the number input's `min`/`max` attributes to match: percent is now `1`-`100` (was `3`-`80`); connections unchanged (`3`-`Math.floor(maxConnectionLimit * 0.8)`). ## Test plan - [x] Unit tests added in `PerformanceSettingsForm.test.ts` covering bounds (1, 100), out-of-range (0, 101, -5, 150), mid-range, string coercion, connections path (no upper zod bound), and enum rejection. All 12 pass. - [x] Manually verify in Auth → Performance settings that entering 0 or 101 in percent mode shows a validation error and blocks save, and that switching to absolute connections still uses the existing bounds. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Refined database pool-size validation and input constraints; percentage unit enforces values between 1–100 and rejects zero, negative, and out-of-range entries. String inputs that represent numbers are coerced where appropriate; unknown unit values are rejected. * **Tests** * Added comprehensive tests for boundary, invalid, string, and unknown-unit cases and for validation error messages. * **Refactor** * Validation moved to a centralized schema and HTML min/max input attributes were removed. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
1 parent
2e3ddc3a52
commit
e8e0842be3
2 files changed
+129
-12
No files matched your search
@@ -0,0 +1,111 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { DatabaseFormSchema } from './PerformanceSettingsForm'
|
||||
|
||||
describe('DatabaseFormSchema', () => {
|
||||
describe('percent unit', () => {
|
||||
it('accepts 1 (lower bound)', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: 1,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'percent',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts 100 (upper bound)', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: 100,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'percent',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts a mid-range value', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: 50,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'percent',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects 0', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: 0,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'percent',
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it('rejects 101', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: 101,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'percent',
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
if (!result.success) {
|
||||
const issue = result.error.issues.find((i) => i.path[0] === 'DB_MAX_POOL_SIZE')
|
||||
expect(issue?.message).toBe('Percentage must be between 1 and 100')
|
||||
}
|
||||
})
|
||||
|
||||
it('rejects negative values', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: -5,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'percent',
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
|
||||
it('coerces string input', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: '75',
|
||||
DB_MAX_POOL_SIZE_UNIT: 'percent',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects out-of-range string input', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: '150',
|
||||
DB_MAX_POOL_SIZE_UNIT: 'percent',
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('connections unit', () => {
|
||||
it('accepts 1 (min)', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: 1,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'connections',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('accepts values above 100 (no upper schema bound for absolute)', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: 500,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'connections',
|
||||
})
|
||||
expect(result.success).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects 0', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: 0,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'connections',
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('unit enum', () => {
|
||||
it('rejects unknown unit values', () => {
|
||||
const result = DatabaseFormSchema.safeParse({
|
||||
DB_MAX_POOL_SIZE: 10,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'bytes',
|
||||
})
|
||||
expect(result.success).toBe(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -46,6 +46,23 @@ const FormSchema = z.object({
|
||||
DB_MAX_POOL_SIZE_UNIT: z.enum(['percent', 'connections']),
|
||||
})
|
||||
|
||||
export const DatabaseFormSchema = z
|
||||
.object({
|
||||
DB_MAX_POOL_SIZE: z.coerce.number().min(1),
|
||||
DB_MAX_POOL_SIZE_UNIT: z.enum(['percent', 'connections']),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.DB_MAX_POOL_SIZE_UNIT === 'percent') {
|
||||
if (data.DB_MAX_POOL_SIZE < 1 || data.DB_MAX_POOL_SIZE > 100) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
path: ['DB_MAX_POOL_SIZE'],
|
||||
message: 'Percentage must be between 1 and 100',
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const PerformanceSettingsForm = () => {
|
||||
const { data: project } = useSelectedProjectQuery()
|
||||
const { hasAccess: hasAccessToPerformance, isLoading: isLoadingEntitlement } =
|
||||
@@ -87,12 +104,7 @@ export const PerformanceSettingsForm = () => {
|
||||
})
|
||||
|
||||
const databaseForm = useForm({
|
||||
resolver: zodResolver(
|
||||
z.object({
|
||||
DB_MAX_POOL_SIZE: FormSchema.shape.DB_MAX_POOL_SIZE,
|
||||
DB_MAX_POOL_SIZE_UNIT: FormSchema.shape.DB_MAX_POOL_SIZE_UNIT,
|
||||
})
|
||||
),
|
||||
resolver: zodResolver(DatabaseFormSchema),
|
||||
defaultValues: {
|
||||
DB_MAX_POOL_SIZE: 10,
|
||||
DB_MAX_POOL_SIZE_UNIT: 'connections',
|
||||
@@ -375,12 +387,6 @@ export const PerformanceSettingsForm = () => {
|
||||
<FormInputGroupInput
|
||||
type="number"
|
||||
{...field}
|
||||
min={3}
|
||||
max={
|
||||
chosenUnit === 'percent'
|
||||
? 80
|
||||
: Math.floor(maxConnectionLimit * 0.8)
|
||||
}
|
||||
disabled={!canUpdateConfig || promptUpgrade}
|
||||
/>
|
||||
<InputGroupAddon align="inline-end">
|
||||
|
||||
Reference in new issue
Block a user