Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions frontend/src/__tests__/settings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
setupSettingsHandlers,
copyToClipboard,
isUnsavedChanges,
openOverrideModal,
} from '../settings';

// Mock the api module
Expand Down Expand Up @@ -1556,4 +1557,87 @@ describe('Settings Module', () => {
expect(isUnsavedChanges()).toBe(true);
});
});

// Issue #135: `default_coverage || 80` rewrote a stored 0 to 80, and the
// next Save persisted 80 without the user touching the field.
describe('default_coverage 0 round-trips through load and save (issue #135)', () => {
const configWithCoverage = (global: Record<string, unknown>, services: unknown[] = []) => ({
global: {
enabled_providers: ['aws'],
notification_email: '',
auto_collect: true,
default_term: 3,
default_payment: 'all-upfront',
notification_days_before: 3,
...global,
},
services,
credentials: {},
});

const serviceCall = (service: string) =>
(api.updateServiceConfig as jest.Mock).mock.calls.find(([p, s]) => p === 'aws' && s === service);

const saveUnchanged = async () => {
(api.updateConfig as jest.Mock).mockResolvedValue({});
(api.updateServiceConfig as jest.Mock).mockClear().mockResolvedValue(undefined);
await saveGlobalSettings({ preventDefault: jest.fn() } as unknown as Event);
};

test('a stored 0 renders as 0 and an unedited Save sends 0, not 80', async () => {
(api.getConfig as jest.Mock).mockResolvedValue(configWithCoverage({ default_coverage: 0 }));
await loadGlobalSettings();

expect((document.getElementById('setting-default-coverage') as HTMLInputElement).value).toBe('0');
expect((document.getElementById('aws-savings-plans-compute-coverage') as HTMLInputElement).value).toBe('0');

await saveUnchanged();

expect((api.updateConfig as jest.Mock).mock.calls[0]![0].default_coverage).toBe(0);
expect(serviceCall('savings-plans-compute')![2].coverage).toBe(0);
expect(serviceCall('ec2')![2].coverage).toBe(0);
});

test('an absent default_coverage still renders the 80 default', async () => {
(api.getConfig as jest.Mock).mockResolvedValue(configWithCoverage({}));
await loadGlobalSettings();

expect((document.getElementById('setting-default-coverage') as HTMLInputElement).value).toBe('80');

await saveUnchanged();

expect((api.updateConfig as jest.Mock).mock.calls[0]![0].default_coverage).toBe(80);
});

test('a per-service coverage of 0 round-trips under a non-zero global', async () => {
(api.getConfig as jest.Mock).mockResolvedValue(configWithCoverage(
{ default_coverage: 50 },
[{ provider: 'aws', service: 'savings-plans-compute', enabled: true, term: 3, payment: 'all-upfront', coverage: 0 }],
));
await loadGlobalSettings();

expect((document.getElementById('aws-savings-plans-compute-coverage') as HTMLInputElement).value).toBe('0');
expect((document.getElementById('aws-savings-plans-ec2instance-coverage') as HTMLInputElement).value).toBe('50');

await saveUnchanged();

expect(serviceCall('savings-plans-compute')![2].coverage).toBe(0);
expect(serviceCall('savings-plans-ec2instance')![2].coverage).toBe(50);
});

test('the override modal inherit label shows the stored 0%', async () => {
const modal = document.createElement('div');
modal.id = 'override-modal';
const covInput = document.createElement('input');
covInput.id = 'override-coverage';
modal.appendChild(covInput);
document.body.appendChild(modal);

(api.getConfig as jest.Mock).mockResolvedValue(configWithCoverage({ default_coverage: 0 }));
await loadGlobalSettings();
openOverrideModal('acc-1', 'aws', [], document.createElement('div'));

expect(covInput.placeholder).toBe('Inherit (currently: 0%)');
});
});
});
4 changes: 2 additions & 2 deletions frontend/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3181,15 +3181,15 @@ export async function loadGlobalSettings(): Promise<void> {
if (paymentSelect) paymentSelect.value = data.global.default_payment || 'all-upfront';

const coverageInput = document.getElementById('setting-default-coverage') as HTMLInputElement | null;
if (coverageInput) coverageInput.value = String(data.global.default_coverage || 80);
if (coverageInput) coverageInput.value = String(data.global.default_coverage ?? 80);

// Cache defaults for the override modal "Inherit (currently: X)" labels
// (issue #112). Must be updated here so reopening the override modal
// after a Settings save reflects any in-session changes.
cachedGlobalDefaults = {
term: data.global.default_term || 3,
payment: data.global.default_payment || 'all-upfront',
coverage: data.global.default_coverage || 80,
coverage: data.global.default_coverage ?? 80,
};

const notifyDaysInput = document.getElementById('setting-notification-days') as HTMLInputElement | null;
Expand Down
Loading