fix(admin): reject quota_tolerance = 0 instead of silently blocking every upload

Zero is inside the documented 0–1 range and catastrophic. The per-user limit is
`free_disk * tolerance / active_uploaders`, so a tolerance of 0 makes every limit 0 and
refuses EVERY upload -- mid-event, with "Du hast dein Upload-Limit für dieses Event
erreicht", an error naming the wrong cause entirely. An admin reaching for an off-switch
wants `storage_quota_enabled`; the rejection now says so.

Rejecting the value rather than raising the floor. A floor of 0.01 was the obvious fix
and it is wrong: very small tolerances are legitimate -- they are how a large disk is
throttled down to a sensible per-guest ceiling, and how the quota specs steer it
(tolerance = target * active / free lands around 1e-5 on the 174 GB volume this suite
runs on). A floor would forbid real configurations, and would have broken the entire
storage-quota describe block, to prevent one typo. Verified: those four tests still pass.

Tests: the rejection, that the stored value is untouched (validation fully precedes any
write), and the mirror -- 0.00001 still round-trips -- so the guard can't quietly become
a floor later.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
fabi
2026-07-29 21:37:39 +02:00
parent 1e5953a566
commit 50d1b5b06d
2 changed files with 51 additions and 0 deletions

View File

@@ -68,6 +68,40 @@ test.describe('Admin — config API', () => {
expect(cfg.privacy_note).toBe(note);
await api.patchConfig(adminToken, { privacy_note: '' });
});
test('quota_tolerance = 0 is rejected, with a pointer to the real off-switch', async ({
api,
adminToken,
}) => {
// Zero is inside the documented 01 range and catastrophic: the per-user limit is
// `free_disk * tolerance / active_uploaders`, so 0 refuses EVERY upload — mid-event, with
// "Du hast dein Upload-Limit für dieses Event erreicht", which names the wrong cause
// entirely. `storage_quota_enabled` is what an admin reaching for an off-switch wants.
const res = await fetch(
(process.env.E2E_FRONTEND_URL ?? 'http://localhost:3101') + '/api/v1/admin/config',
{
method: 'PATCH',
headers: { Authorization: `Bearer ${adminToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ quota_tolerance: '0' }),
}
);
expect(res.status).toBe(400);
expect(
(await res.text()).toLowerCase(),
'the error must name the switch the admin actually wanted'
).toContain('speicher-quote');
// The value is untouched — validation fully precedes any write.
expect((await api.getConfig(adminToken)).quota_tolerance).toBe('0.75');
});
test('a very small quota_tolerance is still accepted', async ({ api, adminToken }) => {
// The mirror. Rejecting 0 must not become a floor: small tolerances are how a large disk is
// throttled to a sensible per-guest ceiling, and how the quota specs steer it (~1e-5 on a
// 174 GB volume). A floor of 0.01 would forbid real configurations to prevent one typo.
await api.patchConfig(adminToken, { quota_tolerance: '0.00001' });
expect((await api.getConfig(adminToken)).quota_tolerance).toBe('0.00001');
await api.patchConfig(adminToken, { quota_tolerance: '0.75' });
});
});
test.describe('Admin — stats', () => {