import moment from 'moment-timezone'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {MetricHistoryFixture} from 'getsentry-test/fixtures/metricHistory'; import { Am3DsEnterpriseSubscriptionFixture, SubscriptionFixture, } from 'getsentry-test/fixtures/subscription'; import {UsageTotalFixture} from 'getsentry-test/fixtures/usageTotal'; import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; import {DataCategory} from 'sentry/types/core'; import {GIGABYTE, RESERVED_BUDGET_QUOTA, UNLIMITED_RESERVED} from 'getsentry/constants'; import SubscriptionStore from 'getsentry/stores/subscriptionStore'; import {OnDemandBudgetMode, type Subscription} from 'getsentry/types'; import UsageTotals, { calculateCategoryOnDemandUsage, calculateCategoryPrepaidUsage, } from 'getsentry/views/subscriptionPage/usageTotals'; describe('Subscription > UsageTotals', function () { const totals = UsageTotalFixture({ accepted: 26, dropped: 10, droppedOverQuota: 7, droppedSpikeProtection: 1, droppedOther: 2, }); const organization = OrganizationFixture(); let subscription!: Subscription; beforeEach(() => { subscription = SubscriptionFixture({ organization, plan: 'am3_business', }); SubscriptionStore.set(organization.slug, subscription); }); afterEach(() => { SubscriptionStore.init(); }); it('calculates error totals and renders them', async function () { render( ); expect(screen.getByText('Errors usage this period')).toBeInTheDocument(); expect(screen.getByTestId('reserved-errors')).toHaveTextContent('100K Reserved'); expect(screen.getByText('26')).toBeInTheDocument(); // Expand usage table await userEvent.click(screen.getByRole('button')); expect( screen.getByRole('row', {name: 'Errors Quantity % of Errors'}) ).toBeInTheDocument(); expect(screen.getByRole('row', {name: 'Accepted 26 72%'})).toBeInTheDocument(); expect(screen.getByRole('row', {name: 'Total Dropped 10 28%'})).toBeInTheDocument(); expect(screen.getByRole('row', {name: 'Over Quota 7 19%'})).toBeInTheDocument(); expect(screen.getByRole('row', {name: 'Spike Protection 1 3%'})).toBeInTheDocument(); expect(screen.getByRole('row', {name: 'Other 2 6%'})).toBeInTheDocument(); }); it('renders transaction event totals with feature', async function () { const am2Subscription = SubscriptionFixture({ organization, plan: 'am2_business', }); SubscriptionStore.set(organization.slug, am2Subscription); render( ); expect(screen.getByTestId('reserved-transactions')).toHaveTextContent( '100K Reserved' ); expect(screen.getByText('26')).toBeInTheDocument(); // Expand usage table await userEvent.click(screen.getByRole('button')); expect( screen.getByRole('columnheader', {name: 'Transaction Events'}) ).toBeInTheDocument(); expect( screen.getByRole('columnheader', {name: 'Profile Events'}) ).toBeInTheDocument(); }); it('does not render transaction event totals without feature', async function () { const am2Subscription = SubscriptionFixture({ organization, plan: 'am2_business', }); SubscriptionStore.set(organization.slug, am2Subscription); render( ); expect(screen.getByTestId('reserved-transactions')).toHaveTextContent( '100K Reserved' ); expect(screen.getByText('26')).toBeInTheDocument(); // Expand usage table await userEvent.click(screen.getByRole('button')); expect( screen.queryByRole('columnheader', {name: 'Transaction Events'}) ).not.toBeInTheDocument(); expect( screen.queryByRole('columnheader', {name: 'Profile Events'}) ).not.toBeInTheDocument(); }); it('renders accepted spans in spend mode with reserved budgets and dynamic sampling', async function () { const dsSubscription = Am3DsEnterpriseSubscriptionFixture({ organization, hadCustomDynamicSampling: true, }); render( ); expect(screen.getByText('Accepted spans spend this period')).toBeInTheDocument(); expect(screen.getByTestId('reserved-spans')).toHaveTextContent( '$100,000.00 Reserved' ); expect(screen.getByText('$40,000')).toBeInTheDocument(); expect(screen.getByText('40% of $100,000')).toBeInTheDocument(); // Expand usage table await userEvent.click(screen.getByRole('button')); expect( screen.getByRole('row', {name: 'Accepted Spans Quantity % of Accepted Spans'}) ).toBeInTheDocument(); expect( screen.getByRole('columnheader', {name: 'Accepted Spans'}) ).toBeInTheDocument(); }); it('renders spans with reserved budgets without dynamic sampling', async function () { const dsSubscription = Am3DsEnterpriseSubscriptionFixture({ organization, hadCustomDynamicSampling: false, }); render( ); expect(screen.getByText('Spans spend this period')).toBeInTheDocument(); expect(screen.getByTestId('reserved-spans')).toHaveTextContent( '$100,000.00 Reserved' ); expect(screen.getByText('$60,000')).toBeInTheDocument(); expect(screen.getByText('60% of $100,000')).toBeInTheDocument(); // Expand usage table await userEvent.click(screen.getByRole('button')); expect( screen.getByRole('row', {name: 'Spans Quantity % of Spans'}) ).toBeInTheDocument(); expect(screen.getByRole('columnheader', {name: 'Spans'})).toBeInTheDocument(); }); it('renders reserved budget categories with gifted budget', function () { const dsSubscription = Am3DsEnterpriseSubscriptionFixture({ organization, hadCustomDynamicSampling: true, }); render( ); expect(screen.getByTestId('gifted-spans')).toHaveTextContent( '$100,000.00 Reserved + $10,000.00 Gifted' ); expect(screen.getByText('55% of $110,000')).toBeInTheDocument(); }); it('renders reserved budget categories with soft cap', function () { const dsSubscription = Am3DsEnterpriseSubscriptionFixture({ organization, hadCustomDynamicSampling: true, }); render( ); expect(screen.getByTestId('reserved-spans')).toHaveTextContent( '$100,000.00 Reserved (On Demand)' ); }); it('formats units', async function () { render( ); expect(screen.getByTestId('reserved-attachments')).toHaveTextContent( '100 GB Reserved' ); // Expand usage table await userEvent.click(screen.getByRole('button')); expect( screen.getByRole('row', {name: 'Attachments Quantity % of Attachments'}) ).toBeInTheDocument(); expect(screen.getByRole('row', {name: 'Accepted 26 B 72%'})).toBeInTheDocument(); expect(screen.getByRole('row', {name: 'Total Dropped 10 B 28%'})).toBeInTheDocument(); expect(screen.getByRole('row', {name: 'Over Quota 7 B 19%'})).toBeInTheDocument(); expect( screen.getByRole('row', {name: 'Spike Protection 1 B 3%'}) ).toBeInTheDocument(); expect(screen.getByRole('row', {name: 'Other 2 B 6%'})).toBeInTheDocument(); }); it('renders default stats with no billing history', function () { render( ); expect(screen.getByText('Transactions usage this period')).toBeInTheDocument(); expect(screen.getByText('0')).toBeInTheDocument(); }); it('renders gifted errors', function () { render( ); expect(screen.getByText('175,000')).toBeInTheDocument(); expect(screen.getByTestId('gifted-errors')).toHaveTextContent( '50K Reserved + 150K Gifted' ); }); it('renders gifted transactions', function () { render( ); expect(screen.getByTestId('gifted-transactions')).toHaveTextContent( '100K Reserved + 200K Gifted' ); }); it('does not render gifted transactions with unlimited quota', function () { render( ); expect(screen.getByTestId('reserved-transactions')).toHaveTextContent('∞ Reserved'); }); it('renders gifted attachments', function () { render( ); expect(screen.getByTestId('gifted-attachments')).toHaveTextContent( '1 GB Reserved + 2 GB Gifted' ); }); it('renders accepted percentage for attachments', function () { const attachments = UsageTotalFixture({accepted: GIGABYTE * 0.6}); render( ); expect(screen.getByText('600 MB')).toBeInTheDocument(); }); it('renders accepted percentage for errors', function () { const errors = UsageTotalFixture({accepted: 92_400}); render( ); expect(screen.getByText('92,400')).toBeInTheDocument(); }); it('renders accepted percentage for transactions', function () { const transactions = UsageTotalFixture({accepted: 200_000}); render( ); expect(screen.getByText('200,000')).toBeInTheDocument(); }); it('renders true forward', function () { render( ); expect(screen.getByTestId('reserved-errors')).toHaveTextContent( '100K Reserved (True Forward)' ); }); it('renders soft cap type on demand', function () { render( ); expect(screen.getByTestId('reserved-errors')).toHaveTextContent( '100K Reserved (On Demand)' ); }); it('renders soft cap type true forward', function () { render( ); expect(screen.getByTestId('reserved-errors')).toHaveTextContent( '100K Reserved (True Forward)' ); }); it('renders true forward with gifted amount', function () { render( ); expect(screen.getByTestId('gifted-errors')).toHaveTextContent( '100K Reserved (True Forward) + 50K Gifted' ); }); it('renders soft cap type on demand with gifted amount', function () { render( ); expect(screen.getByTestId('gifted-errors')).toHaveTextContent( '100K Reserved (On Demand) + 50K Gifted' ); }); it('renders soft cap type true forward with gifted amount', function () { render( ); expect(screen.getByTestId('gifted-errors')).toHaveTextContent( '100K Reserved (True Forward) + 50K Gifted' ); }); it('renders usage card with Trial if an active trial exists', function () { subscription.productTrials = [ { category: DataCategory.REPLAYS, isStarted: true, reasonCode: 1001, startDate: moment().utc().subtract(10, 'days').format(), endDate: moment().utc().add(20, 'days').format(), }, ]; render( ); expect(screen.getByText('Replays trial usage this period')).toBeInTheDocument(); expect(screen.queryByText('Start trial')).not.toBeInTheDocument(); }); it('renders usage card with Start trial button if a potential trial exists', function () { subscription.productTrials = [ { category: DataCategory.REPLAYS, isStarted: false, reasonCode: 1001, startDate: undefined, endDate: moment().utc().add(20, 'years').format(), }, ]; render( ); expect(screen.getByText('Replays usage this period')).toBeInTheDocument(); expect(screen.queryByText('Replays Trial')).not.toBeInTheDocument(); expect(screen.getByText('Start trial')).toBeInTheDocument(); }); it('renders usage card as normal if trial has ended', function () { subscription.productTrials = [ { category: DataCategory.REPLAYS, isStarted: true, reasonCode: 1001, startDate: moment().utc().subtract(10, 'days').format(), endDate: moment().utc().subtract(1, 'days').format(), }, ]; render( ); expect(screen.getByText('Replays usage this period')).toBeInTheDocument(); expect(screen.queryByText('Replays Trial')).not.toBeInTheDocument(); expect(screen.queryByText('Start trial')).not.toBeInTheDocument(); }); it('renders total usage in dollars', () => { const spendSubscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', }); const prepaidUsage = 100_000; const prepaid = prepaidUsage * 2; spendSubscription.categories.errors = MetricHistoryFixture({ prepaid, reserved: prepaid, }); const spendTotals = UsageTotalFixture({ accepted: prepaidUsage, dropped: 100_000, }); render( ); expect(screen.getByText('Errors spend this period')).toBeInTheDocument(); expect(screen.getByText('$32 Included in Subscription')).toBeInTheDocument(); expect(screen.getByText('50% of $32')).toBeInTheDocument(); }); it('renders total usage', () => { const spendSubscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', }); const prepaidUsage = 100_000; const prepaid = prepaidUsage * 2; spendSubscription.categories.errors = MetricHistoryFixture({ prepaid, reserved: prepaid, }); const spendTotals = UsageTotalFixture({ accepted: prepaidUsage, dropped: 100_000, }); render( ); expect(screen.getByText('Errors usage this period')).toBeInTheDocument(); const totalUsageContainer = screen.getByText('Total Usage'); expect(totalUsageContainer).toBeInTheDocument(); expect(totalUsageContainer.parentElement).toHaveTextContent('100,000Total Usage'); expect(screen.getByText('100K of 200K')).toBeInTheDocument(); }); it('displays plan usage when there is no spend', () => { render( ); // Spend is 0, so we should display included plan usage expect(screen.getByText('Included in Subscription')).toBeInTheDocument(); expect(screen.getByText('26 of 100K')).toBeInTheDocument(); }); it('displays shared On-Demand max available when another category has used some', () => { organization.features = ['ondemand-budgets']; const onDemandCategoryMax = 8000; const onDemandSubscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', onDemandBudgets: { budgetMode: OnDemandBudgetMode.SHARED, enabled: true, onDemandSpendUsed: onDemandCategoryMax, sharedMaxBudget: onDemandCategoryMax, }, }); const prepaid = 200_000; onDemandSubscription.categories.errors = MetricHistoryFixture({ prepaid, reserved: prepaid, // Errors uses half of the shared on demand budget onDemandSpendUsed: onDemandCategoryMax / 2, }); // Performance uses some as well onDemandSubscription.categories.transactions = MetricHistoryFixture({ onDemandSpendUsed: onDemandCategoryMax / 4, }); render( ); // Errors has used 50% and it could spend another $20 since performance has used $20 expect(screen.getByText('$40 of $60 ($80 max)')).toBeInTheDocument(); expect( screen.getByText('$32 Included in Subscription + $40 On-Demand') ).toBeInTheDocument(); }); it('displays pay-as-you-go max available when another category has used some', () => { organization.features = ['ondemand-budgets']; const onDemandCategoryMax = 8000; const paygSubscription = SubscriptionFixture({ organization, plan: 'am3_team', planTier: 'am3', onDemandMaxSpend: 8000, }); const prepaid = 200_000; paygSubscription.categories.errors = MetricHistoryFixture({ prepaid, reserved: prepaid, // Errors uses half of the shared on demand budget onDemandSpendUsed: onDemandCategoryMax / 2, }); // Spans uses some as well paygSubscription.categories.spans = MetricHistoryFixture({ onDemandSpendUsed: onDemandCategoryMax / 4, }); render( ); // Errors has used 50% and it could spend another $20 since performance has used $20 expect(screen.getByText('$40 of $60 ($80 max)')).toBeInTheDocument(); expect( screen.getByText('$51 Included in Subscription + $40 Pay-as-you-go') ).toBeInTheDocument(); }); it('renders onDemandUsage when over prepaidTotal', function () { const usageTotals = UsageTotalFixture({ accepted: 150_000, dropped: 10, droppedOverQuota: 7, droppedSpikeProtection: 1, droppedOther: 2, }); organization.features = ['ondemand-budgets']; const paygSubscription = SubscriptionFixture({ organization, plan: 'am3_team', planTier: 'am3', onDemandMaxSpend: 10_00, // $10.00 }); paygSubscription.categories.errors = MetricHistoryFixture({ reserved: 100_000, onDemandQuantity: 50_000, }); render( ); // Test the header section expect(screen.getByText('Errors usage this period')).toBeInTheDocument(); expect(screen.getByTestId('reserved-errors')).toHaveTextContent('100K Reserved'); // Test the Pay-as-you-go section expect(screen.getByText('Pay-as-you-go')).toBeInTheDocument(); expect(screen.getByText('50,000')).toBeInTheDocument(); // On-demand usage amount // Test the included subscription section const includedSection = screen.getByText('Included in Subscription').parentElement; expect(includedSection).toHaveTextContent('100K of 100K'); // Test the total usage section const totalUsageSection = screen.getByText('Total Usage').parentElement; expect(totalUsageSection).toHaveTextContent('150,000'); // Verify expand button exists expect(screen.getByLabelText('Expand usage totals')).toBeInTheDocument(); // Test the usage bars are rendered const usageBarContainer = document.querySelector('[class*="PlanUseBarContainer"]'); expect(usageBarContainer).toBeInTheDocument(); // Verify both prepaid and on-demand bars exist const usageBars = document.querySelectorAll( '[class*="PlanUseBarGroup"] > [class*="PlanUseBar"]' ); expect(usageBars).toHaveLength(2); }); it('does not render onDemandUsage when under prepaidTotal', function () { const usageTotals = UsageTotalFixture({ accepted: 100_000, dropped: 10, droppedOverQuota: 7, droppedSpikeProtection: 1, droppedOther: 2, }); organization.features = ['ondemand-budgets']; const paygSubscription = SubscriptionFixture({ organization, plan: 'am3_team', planTier: 'am3', onDemandMaxSpend: 10_00, // $10.00 }); paygSubscription.categories.errors = MetricHistoryFixture({ reserved: 100_000, onDemandQuantity: 0, }); render( ); // Test the header section expect(screen.getByText('Errors usage this period')).toBeInTheDocument(); expect(screen.getByTestId('reserved-errors')).toHaveTextContent('100K Reserved'); // Test the Pay-as-you-go section expect(screen.getByText('Pay-as-you-go')).toBeInTheDocument(); const paygUsage = screen.getByText('0', { selector: '[class*="LegendContainer"] [class*="LegendPrice"]', }); expect(paygUsage).toBeInTheDocument(); expect(paygUsage).toHaveTextContent(/^0$/); // On-demand usage amount // Test the included subscription section const includedSection = screen.getByText('Included in Subscription').parentElement; expect(includedSection).toHaveTextContent('100K of 100K'); // Test the total usage section const totalUsageSection = screen.getByText('Total Usage').parentElement; expect(totalUsageSection).toHaveTextContent('100,000'); // Verify expand button exists expect(screen.getByLabelText('Expand usage totals')).toBeInTheDocument(); // Test the usage bars are rendered const usageBarContainer = document.querySelector('[class*="PlanUseBarContainer"]'); expect(usageBarContainer).toBeInTheDocument(); // Verify both prepaid and on-demand bars exist const usageBars = document.querySelectorAll( '[class*="PlanUseBarGroup"] > [class*="PlanUseBar"]' ); expect(usageBars).toHaveLength(2); }); }); describe('calculateCategoryPrepaidUsage', () => { const organization = OrganizationFixture(); it('calculates prepaid usage of 50%', () => { const subscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', }); const prepaidUsage = 150_000; const prepaid = prepaidUsage * 2; subscription.categories.errors = MetricHistoryFixture({ prepaid, reserved: prepaid, }); const totals = UsageTotalFixture({ accepted: prepaidUsage, dropped: 100_000, }); expect( calculateCategoryPrepaidUsage('errors', subscription, totals, prepaid) ).toEqual({ onDemandUsage: 0, prepaidPercentUsed: 50, prepaidPrice: 5000, prepaidSpend: 2500, prepaidUsage, }); }); it('converts annual prepaid price to monthly', () => { const subscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', }); subscription.planDetails.billingInterval = 'annual'; const prepaidPrice = 10000; subscription.planDetails.planCategories.errors = [ {events: 100_000, price: prepaidPrice * 12, unitPrice: 0.1, onDemandPrice: 0.2}, ]; const totals = UsageTotalFixture(); expect( calculateCategoryPrepaidUsage('errors', subscription, totals, prepaidPrice) ).toEqual({ onDemandUsage: 0, prepaidPercentUsed: 0, prepaidPrice, prepaidSpend: 0, prepaidUsage: 0, }); }); it('should not error when prices are not available', () => { const subscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', }); const prepaidPrice = 0; delete subscription.planDetails.planCategories.monitorSeats; const totals = UsageTotalFixture(); expect( calculateCategoryPrepaidUsage('monitorSeats', subscription, totals, prepaidPrice) ).toEqual({ onDemandUsage: 0, prepaidPercentUsed: 0, prepaidPrice: 0, prepaidSpend: 0, prepaidUsage: 0, }); }); it('on-demand usage should be zero for unlimited reserve', () => { const subscription = SubscriptionFixture({ organization, plan: 'am3_t', planTier: 'am3', }); const prepaidUsage = 10; const prepaid = -1; subscription.categories.monitorSeats = MetricHistoryFixture({ prepaid, reserved: prepaid, }); const totals = UsageTotalFixture({ accepted: prepaidUsage, dropped: 0, }); expect( calculateCategoryPrepaidUsage('monitorSeats', subscription, totals, prepaid) ).toEqual({ onDemandUsage: 0, prepaidPercentUsed: 0, prepaidPrice: 0, prepaidSpend: 0, prepaidUsage, }); }); it('calculates onDemandUsage using categoryInfo.onDemandQuantity when over prepaidTotal', () => { const subscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', }); const prepaid = 100_000; const totals = UsageTotalFixture({ accepted: 150_000, // totals.accepted > prepaidTotal }); subscription.categories.errors = MetricHistoryFixture({ reserved: prepaid, onDemandQuantity: 50_000, }); const result = calculateCategoryPrepaidUsage('errors', subscription, totals, prepaid); expect(result.onDemandUsage).toBe(50_000); expect(result.prepaidUsage).toBe(100_000); }); it('sets onDemandUsage to zero when totals.accepted <= prepaidTotal', () => { const subscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', }); const prepaid = 100_000; const totals = UsageTotalFixture({ accepted: 80_000, // totals.accepted <= prepaidTotal }); subscription.categories.errors = MetricHistoryFixture({ reserved: prepaid, onDemandQuantity: 0, }); const result = calculateCategoryPrepaidUsage('errors', subscription, totals, prepaid); expect(result.onDemandUsage).toBe(0); expect(result.prepaidUsage).toBe(80_000); }); it('sets onDemandUsage to zero when isUnlimitedReserved(prepaidTotal) is true', () => { const subscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', }); const prepaid = UNLIMITED_RESERVED; const totals = UsageTotalFixture({ accepted: 150_000, }); subscription.categories.errors = MetricHistoryFixture({ reserved: prepaid, onDemandQuantity: 50_000, }); const result = calculateCategoryPrepaidUsage('errors', subscription, totals, prepaid); expect(result.onDemandUsage).toBe(0); expect(result.prepaidUsage).toBe(150_000); }); it('calculates for reserved budgets with reserved spend', function () { const subscription = Am3DsEnterpriseSubscriptionFixture({organization}); const prepaid = 100_000_00; const totals = UsageTotalFixture({ accepted: 150_000, }); subscription.categories.spans!.onDemandQuantity = 50_000; const result = calculateCategoryPrepaidUsage( 'spans', subscription, totals, prepaid, undefined, 10_000_00 ); expect(result).toEqual({ onDemandUsage: 0, prepaidPercentUsed: 10, prepaidPrice: 100_000_00, prepaidSpend: 10_000_00, prepaidUsage: 150_000, }); const result2 = calculateCategoryPrepaidUsage( 'spans', subscription, totals, prepaid, undefined, 100_000_00 ); expect(result2).toEqual({ onDemandUsage: 50_000, prepaidPercentUsed: 100, prepaidPrice: 100_000_00, prepaidSpend: 100_000_00, prepaidUsage: 100_000, }); const result3 = calculateCategoryPrepaidUsage( 'spans', subscription, totals, prepaid, undefined, 0 ); expect(result3).toEqual({ onDemandUsage: 0, prepaidPercentUsed: 0, prepaidPrice: 100_000_00, prepaidSpend: 0, prepaidUsage: 150_000, }); }); it('calculates for reserved budgets with reserved cpe', function () { const subscription = Am3DsEnterpriseSubscriptionFixture({organization}); const prepaid = 100_000_00; const totals = UsageTotalFixture({ accepted: 150_000, }); subscription.categories.spans!.onDemandQuantity = 50_000; const result = calculateCategoryPrepaidUsage( 'spans', subscription, totals, prepaid, 100 ); expect(result).toEqual({ onDemandUsage: 50_000, prepaidPercentUsed: 100, prepaidPrice: 100_000_00, prepaidSpend: 100_000_00, prepaidUsage: 100_000, }); }); }); describe('calculateCategoryOnDemandUsage', () => { const organization = OrganizationFixture(); it('calculates on demand shared fully used by this category', () => { const onDemandCategoryMax = 8000; const subscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', onDemandBudgets: { budgetMode: OnDemandBudgetMode.SHARED, enabled: true, onDemandSpendUsed: onDemandCategoryMax, sharedMaxBudget: onDemandCategoryMax, }, }); subscription.categories.errors = MetricHistoryFixture({ onDemandSpendUsed: onDemandCategoryMax, }); expect(calculateCategoryOnDemandUsage('errors', subscription)).toEqual({ onDemandTotalAvailable: onDemandCategoryMax, onDemandCategoryMax, onDemandCategorySpend: onDemandCategoryMax, ondemandPercentUsed: 100, }); }); it('calculates on demand shared used 100% by another category', () => { const onDemandCategoryMax = 8000; const subscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', onDemandBudgets: { budgetMode: OnDemandBudgetMode.SHARED, enabled: true, onDemandSpendUsed: onDemandCategoryMax, sharedMaxBudget: onDemandCategoryMax, }, }); subscription.categories.errors = MetricHistoryFixture({ onDemandSpendUsed: 0, }); expect(calculateCategoryOnDemandUsage('errors', subscription)).toEqual({ onDemandTotalAvailable: onDemandCategoryMax, onDemandCategoryMax, onDemandCategorySpend: 0, ondemandPercentUsed: 0, }); }); it('calculates on demand shared used 50% by another category', () => { const onDemandCategoryMax = 8000; const subscription = SubscriptionFixture({ organization, plan: 'am2_team', planTier: 'am2', onDemandBudgets: { budgetMode: OnDemandBudgetMode.SHARED, enabled: true, onDemandSpendUsed: onDemandCategoryMax, sharedMaxBudget: onDemandCategoryMax, }, }); // Replays uses half of the shared on demand budget subscription.categories.replays = MetricHistoryFixture({ onDemandSpendUsed: onDemandCategoryMax / 2, }); expect(calculateCategoryOnDemandUsage('errors', subscription)).toEqual({ onDemandTotalAvailable: onDemandCategoryMax, // Half is left for other categories onDemandCategoryMax: onDemandCategoryMax / 2, onDemandCategorySpend: 0, ondemandPercentUsed: 0, }); }); });