diff --git a/frontend/src/__tests__/settings-accounts.test.ts b/frontend/src/__tests__/settings-accounts.test.ts index e28f2860..fba3e0e9 100644 --- a/frontend/src/__tests__/settings-accounts.test.ts +++ b/frontend/src/__tests__/settings-accounts.test.ts @@ -15,7 +15,10 @@ jest.mock('../api', () => ({ updateAccount: jest.fn(), deleteAccount: jest.fn(), testAccountCredentials: jest.fn(), - saveAccountCredentials: jest.fn() + saveAccountCredentials: jest.fn(), + listAccountServiceOverrides: jest.fn(), + saveAccountServiceOverride: jest.fn(), + deleteAccountServiceOverride: jest.fn() })); const mockShowToast = jest.fn<{ dismiss: () => void }, [unknown]>(() => ({ dismiss: jest.fn() })); @@ -472,3 +475,93 @@ describe('Account form submit', () => { }); }); }); + +// --------------------------------------------------------------------------- +// Account overrides panel — payment option selector (issue #23) +// --------------------------------------------------------------------------- + +describe('Overrides panel — AWS payment selector', () => { + /** + * Render the AWS accounts list and expand the first account's overrides + * panel so the panel DOM is populated with whatever + * listAccountServiceOverrides has been mocked to return. + */ + async function openOverridesPanel(accountId = 'acc-1'): Promise { + (api.listAccounts as jest.Mock).mockResolvedValue([ + { id: accountId, name: 'Prod', provider: 'aws', external_id: '111', enabled: true }, + ]); + await loadAccountsForProvider('aws'); + const overridesBtn = document.querySelector( + `button[aria-label="Service overrides for Prod (111)"]`, + ) as HTMLButtonElement | null; + expect(overridesBtn).not.toBeNull(); + overridesBtn!.click(); + // loadOverridesPanel is async; let microtasks flush. + await new Promise(r => setTimeout(r, 0)); + const panel = document.querySelector('.account-overrides-panel') as HTMLElement | null; + expect(panel).not.toBeNull(); + return panel!; + } + + beforeEach(() => { + buildAccountsDOM(); + jest.clearAllMocks(); + }); + + test('renders payment ', async () => { + (api.listAccountServiceOverrides as jest.Mock).mockResolvedValue([ + { id: 'o1', account_id: 'acc-1', provider: 'azure', service: 'vm', payment: 'all-upfront' }, + ]); + + const panel = await openOverridesPanel('acc-1'); + expect(panel.querySelector('select.override-payment-select')).toBeNull(); + expect(panel.textContent).toContain('all-upfront'); + }); +}); diff --git a/frontend/src/settings.ts b/frontend/src/settings.ts index aaf49bf8..88cc7a06 100644 --- a/frontend/src/settings.ts +++ b/frontend/src/settings.ts @@ -421,6 +421,108 @@ function renderAccountsList( panels.forEach((p) => container.appendChild(p)); } +// AWS payment-option choices, kept in sync with the per-service Purchasing +// selectors in frontend/src/index.html (e.g. #aws-ec2-payment). Centralised +// here so the override editor and the global selectors can't drift. +const AWS_PAYMENT_OPTIONS: ReadonlyArray<{ value: string; label: string }> = [ + { value: 'no-upfront', label: 'No Upfront' }, + { value: 'partial-upfront', label: 'Partial Upfront' }, + { value: 'all-upfront', label: 'All Upfront' }, +]; + +/** + * Build the per-row Payment for AWS (the only provider whose + // reservations support distinct payment options); read-only text for + // Azure/GCP. Issue #23. + const paymentTd = tr.insertCell(); + if (o.provider === 'aws') { + paymentTd.appendChild(buildPaymentOverrideSelect(accountId, o, panel)); + } else { + paymentTd.textContent = o.payment ?? '\u2014'; + } + + const coverageTd = tr.insertCell(); + coverageTd.textContent = o.coverage !== undefined ? `${o.coverage}%` : '\u2014'; + const actionTd = tr.insertCell(); const resetBtn = document.createElement('button'); resetBtn.type = 'button';