From 3bd0b0cd2ba566bb62ede279b8fe2efa42e8e80c Mon Sep 17 00:00:00 2001 From: aiirvizionz Date: Tue, 21 Jul 2026 15:59:33 -0600 Subject: [PATCH] fix(cloud-runpod): reject non-decimal sizing options --- packages/cloud/runpod/src/index.test.ts | 22 ++++++++++++++++++++++ packages/cloud/runpod/src/index.ts | 13 +++++++++++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/cloud/runpod/src/index.test.ts b/packages/cloud/runpod/src/index.test.ts index 90bd06e8..5964b855 100644 --- a/packages/cloud/runpod/src/index.test.ts +++ b/packages/cloud/runpod/src/index.test.ts @@ -229,6 +229,28 @@ describe('RunPod cloud adapter', () => { expect(fetchMock).not.toHaveBeenCalled(); }); + it('rejects non-decimal sizing strings before provisioning', async () => { + const fetchMock = vi.fn(); + vi.stubGlobal('fetch', fetchMock); + + await expect(adapter.provision( + provisionCtx(), + { kind: 'gpu', gpu: { model: 'NVIDIA RTX A6000', count: 1 } }, + { hourlyPrice: 0.5, imageName: 'runpod/pytorch', volumeInGb: '1e2' }, + )).rejects.toThrow('RunPod volumeInGb must be a plain decimal number'); + await expect(adapter.provision( + provisionCtx(), + { kind: 'gpu', gpu: { model: 'NVIDIA RTX A6000', count: 1 } }, + { hourlyPrice: 0.5, imageName: 'runpod/pytorch', containerDiskInGb: '0x10' }, + )).rejects.toThrow('RunPod containerDiskInGb must be a plain decimal number'); + await expect(adapter.provision( + provisionCtx(), + { kind: 'gpu', gpu: { model: 'NVIDIA RTX A6000', count: 1 }, cpu: '-1' as any }, + { hourlyPrice: 0.5, imageName: 'runpod/pytorch' }, + )).rejects.toThrow('RunPod minVcpuCount must be a plain decimal number'); + expect(fetchMock).not.toHaveBeenCalled(); + }); + it('does not call RunPod for dry-run provisioning without hourlyPrice', async () => { const fetchMock = vi.fn(); vi.stubGlobal('fetch', fetchMock); diff --git a/packages/cloud/runpod/src/index.ts b/packages/cloud/runpod/src/index.ts index 6fb11a4f..ff1d4ac5 100644 --- a/packages/cloud/runpod/src/index.ts +++ b/packages/cloud/runpod/src/index.ts @@ -428,17 +428,26 @@ function safeName(value: string): string { function optionalPositiveNumber(value: Numberish | undefined, label: string): number | undefined { if (value === undefined) return undefined; - const number = Number(value); + const number = plainDecimalNumber(value, label); if (!Number.isFinite(number) || number <= 0) throw new Error(`RunPod ${label} must be a positive number`); return number; } function nonNegativeNumber(value: Numberish, label: string): number { - const number = Number(value); + const number = plainDecimalNumber(value, label); if (!Number.isFinite(number) || number < 0) throw new Error(`RunPod ${label} must be a non-negative number`); return number; } +function plainDecimalNumber(value: Numberish, label: string): number { + if (typeof value === 'number') return value; + const text = value.trim(); + if (!/^(?:0|[1-9]\d*|0?\.\d+|[1-9]\d*\.\d+)$/.test(text)) { + throw new Error(`RunPod ${label} must be a plain decimal number`); + } + return Number(text); +} + function graphqlError(payload: RunpodGraphqlResponse): string { return payload.errors?.map((error) => error.message).filter(Boolean).join('; ') ?? ''; }