Align admin optional-services billing UI with runtimes and bill RAM proportionally.
Split optional pricing vs deploy defaults and move custom domain to its own card; parse Mi as decimal GB so fractional memory scales linearly with per-GB rates. Deploy wizard hides env and optional services for WordPress and sanitizes create payloads. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -76,6 +76,29 @@ describe('PricingCatalogService', () => {
|
||||
expect(service.parseCpuToCores('2')).toBe(2);
|
||||
});
|
||||
|
||||
it('parses memory Mi to decimal GB for proportional billing (500Mi = 0.5 GB)', () => {
|
||||
expect(service.parseMemoryToGb('500Mi')).toBe(0.5);
|
||||
expect(service.parseMemoryToGb('1000Mi')).toBe(1);
|
||||
expect(service.parseMemoryToGb('1Gi')).toBe(1);
|
||||
});
|
||||
|
||||
it('bills memory proportional to decimal GB (500Mi at half the per-GB monthly rate)', () => {
|
||||
const rates = [
|
||||
{
|
||||
runtime: AppRuntime.NODEJS,
|
||||
resourceType: PricingResourceType.MEMORY_PER_GB,
|
||||
hourlyPrice: 0,
|
||||
monthlyPrice: 10_000,
|
||||
yearlyPrice: 0,
|
||||
isActive: true,
|
||||
},
|
||||
] as PricingRate[];
|
||||
|
||||
const dto = { ...baseDto(), memoryLimit: '500Mi' };
|
||||
const result = service.computeTotalsWithRates(dto, rates, emptyOptional());
|
||||
expect(result.monthly).toBe(5000);
|
||||
});
|
||||
|
||||
it('computes CPU line with cycle-native prices (no conversion)', () => {
|
||||
const rates = [
|
||||
{
|
||||
|
||||
@@ -580,7 +580,7 @@ export class PricingCatalogService implements OnModuleInit {
|
||||
case PricingResourceType.CPU_PER_CORE:
|
||||
return `CPU (${quantity.toFixed(2)} core)`;
|
||||
case PricingResourceType.MEMORY_PER_GB:
|
||||
return `Memory (${quantity.toFixed(2)} GB)`;
|
||||
return `Memory (${quantity.toFixed(3)} GB billable)`;
|
||||
case PricingResourceType.STORAGE_PER_GB:
|
||||
return `Storage (${quantity} GB)`;
|
||||
default:
|
||||
@@ -616,12 +616,19 @@ export class PricingCatalogService implements OnModuleInit {
|
||||
return parseFloat(cpu) || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Billable memory quantity for rates labeled "per GB".
|
||||
* Uses decimal GB so fractional Mi scales linearly (e.g. 500Mi → 0.5 × per-GB price).
|
||||
* Gi values are treated as GB-sized billing units (1Gi → 1 unit).
|
||||
*/
|
||||
parseMemoryToGb(memory: string): number {
|
||||
if (!memory) return 0;
|
||||
if (memory.endsWith('Gi')) return parseFloat(memory);
|
||||
if (memory.endsWith('Mi')) return parseFloat(memory) / 1024;
|
||||
if (memory.endsWith('Ki')) return parseFloat(memory) / (1024 * 1024);
|
||||
return parseFloat(memory) / (1024 * 1024 * 1024);
|
||||
const m = memory.trim();
|
||||
if (m.endsWith('Gi')) return parseFloat(m) || 0;
|
||||
if (m.endsWith('Mi')) return (parseFloat(m) || 0) / 1000;
|
||||
if (m.endsWith('Ki')) return (parseFloat(m) || 0) / 1_000_000;
|
||||
const n = parseFloat(m);
|
||||
return Number.isFinite(n) ? n / (1024 * 1024 * 1024) : 0;
|
||||
}
|
||||
|
||||
private async upsertRuntimeRate(runtime: AppRuntime, row: PricingRateRow) {
|
||||
|
||||
Reference in New Issue
Block a user