Files
supabase/apps/studio/components/interfaces/DiskManagement/DiskManagement.test.ts
Joey Freeland 394ccf4e27 fix(studio): correct gp3 IOPS validation for small disks (#45957)
- Fix gp3 max-IOPS calculation: was `3000 × size`, is slightly more complicated. The old multiplier let Studio accept IOPS values that the platform API would reject.
- Block IOPS/throughput edits when the disk is smaller than the minimum required for custom IOPS (6 GB for gp3). Previously, a throughput-only change on a 2 GB disk would still ship `provisionedIOPS: 3000` in the payload and get rejected with: `Invalid IOPS value for gp3 volume 3000. Minimum is 3000. Max is 500 IOPS per GB or 16,000, whichever is lower.`
- Show a NoticeBar prompting users to grow the disk first, with a one-click button to bump it to the minimum (mirrors the existing "Change to LARGE Compute" pattern).
- Surface the helpful "Larger Disk size required" message for all disk sizes instead of a cryptic "Invalid IOPS value due to invalid disk size" fallback for disks < 8 GB.
2026-05-20 08:39:39 -04:00

172 lines
5.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, test } from 'vitest'
import {
calculateBaselineIopsForComputeSize,
calculateComputeSizeRequiredForIops,
calculateDiskSizePrice,
calculateIOPSPrice,
calculateMaxIopsAllowedForDiskSizeWithGp3,
calculateMaxIopsForComputeSize,
calculateThroughputPrice,
mapAddOnVariantIdToComputeSize,
mapComputeSizeNameToAddonVariantId,
} from './DiskManagement.utils'
import { DiskType } from './ui/DiskManagement.constants'
describe('DiskManagement utils', () => {
describe('mapComputeSizeNameToAddonVariantId', () => {
test('maps known infra sizes to addon variant ids', () => {
expect(mapComputeSizeNameToAddonVariantId('4xlarge')).toBe('ci_4xlarge')
})
test('falls back to nano for unknown infra sizes', () => {
// @ts-expect-error intentional invalid value for runtime guard
expect(mapComputeSizeNameToAddonVariantId('unknown-size')).toBe('ci_nano')
})
})
describe('mapAddOnVariantIdToComputeSize', () => {
test('maps known addon ids to display names', () => {
expect(mapAddOnVariantIdToComputeSize('ci_4xlarge')).toBe('4XL')
})
test('falls back to Nano on invalid addon id', () => {
// @ts-expect-error intentional invalid value for runtime guard
expect(mapAddOnVariantIdToComputeSize('ci_invalid')).toBe('Nano')
})
})
describe('calculateBaselineIopsForComputeSize / calculateMaxIopsForComputeSize', () => {
test('returns 0 for invalid compute ids', () => {
expect(calculateBaselineIopsForComputeSize('invalid')).toBe(0)
expect(calculateMaxIopsForComputeSize('invalid')).toBe(0)
})
test('returns baseline and max for valid compute ids', () => {
expect(calculateBaselineIopsForComputeSize('ci_2xlarge')).toBe(12000)
expect(calculateMaxIopsForComputeSize('ci_2xlarge')).toBe(20000)
})
})
describe('calculateComputeSizeRequiredForIops', () => {
test('returns smallest size that satisfies requested IOPS', () => {
expect(calculateComputeSizeRequiredForIops(500)).toBe('ci_nano')
expect(calculateComputeSizeRequiredForIops(19000)).toBe('ci_large')
expect(calculateComputeSizeRequiredForIops(45000)).toBe('ci_12xlarge')
})
test('falls back to largest size when exceeding known max', () => {
const fallback = calculateComputeSizeRequiredForIops(500000)
expect([
'ci_48xlarge',
'ci_48xlarge_optimized_cpu',
'ci_48xlarge_optimized_memory',
'ci_48xlarge_high_memory',
]).toContain(fallback)
})
})
})
describe('calculateMaxIopsAllowedForDiskSizeWithGp3', () => {
// Regression: old code returned `3000 * size`, letting a 2 GB disk request 6000 IOPS
// which the platform rejects. The real ceiling is 500 IOPS/GB capped at 16 000.
test('caps a sub-6 GB disk at the 3000 IOPS floor (not 3000 × size)', () => {
expect(calculateMaxIopsAllowedForDiskSizeWithGp3(2)).toBe(3000)
})
test('caps large disks at 16 000 IOPS', () => {
expect(calculateMaxIopsAllowedForDiskSizeWithGp3(100)).toBe(16000)
})
})
describe('DiskManagement.utils.ts:calculateDiskSizePrice', () => {
test('GP3 with 8GB to GP3 with 10GB for pro plan', () => {
const result = calculateDiskSizePrice({
planId: 'pro',
oldSize: 8,
oldStorageType: DiskType.GP3,
newSize: 10,
newStorageType: DiskType.GP3,
})
expect(result.oldPrice).toBe('0.00')
expect(result.newPrice).toBe('0.25')
})
test('IO2 with 8GB to IO2 with 10GB for pro plan', () => {
const result = calculateDiskSizePrice({
planId: 'pro',
oldSize: 8,
oldStorageType: DiskType.IO2,
newSize: 10,
newStorageType: DiskType.IO2,
})
expect(result.oldPrice).toBe('1.56')
expect(result.newPrice).toBe('1.95')
})
test('GP3 with 8GB to GP3 with 10GB, with 2 replicas', () => {
const result = calculateDiskSizePrice({
planId: 'pro',
oldSize: 8,
oldStorageType: DiskType.GP3,
newSize: 10,
newStorageType: DiskType.GP3,
numReplicas: 2,
})
expect(result.oldPrice).toBe('2.50')
expect(result.newPrice).toBe('3.38')
})
})
describe('DiskManagement.utils.ts:calculateIOPSPrice', () => {
test('GP3 with 3000 to IO2 with 3000', () => {
const result = calculateIOPSPrice({
oldStorageType: DiskType.GP3,
oldProvisionedIOPS: 3000,
newStorageType: DiskType.IO2,
newProvisionedIOPS: 3000,
})
expect(result.oldPrice).toBe('0.00')
expect(result.newPrice).toBe('357.00')
})
test('GP3 with 3000 to GP3 with 5000', () => {
const result = calculateIOPSPrice({
oldStorageType: DiskType.GP3,
oldProvisionedIOPS: 3000,
newStorageType: DiskType.GP3,
newProvisionedIOPS: 5000,
})
expect(result.oldPrice).toBe('0.00')
expect(result.newPrice).toBe('48.00')
})
test('IO2 with 3000 to IO2 with 5000', () => {
const result = calculateIOPSPrice({
oldStorageType: DiskType.IO2,
oldProvisionedIOPS: 3000,
newStorageType: DiskType.IO2,
newProvisionedIOPS: 5000,
})
expect(result.oldPrice).toBe('357.00')
expect(result.newPrice).toBe('595.00')
})
})
describe('DiskManagement.utils.ts:calculateThroughputPrice', () => {
test('GP3 with 125 MB/s 150 MB/s', () => {
const result = calculateThroughputPrice({
storageType: DiskType.GP3,
oldThroughput: 125,
newThroughput: 150,
})
expect(result.oldPrice).toBe('0.00')
expect(result.newPrice).toBe('2.38')
})
test('IO1 with 125 MB/s 150 MB/s', () => {
const result = calculateThroughputPrice({
storageType: DiskType.IO2,
oldThroughput: 125,
newThroughput: 150,
})
expect(result.oldPrice).toBe('0.00')
expect(result.newPrice).toBe('0.00')
})
})