fix(admin): let a setting whose default is empty actually be cleared (#280)
intakeNotifyEmail and intakeCeilingResetAt both document empty as their default and as a working configuration — no notification address, and no ceiling reset recorded. The validator refused every empty text value, so either could be set and then never removed through the admin at all; the only way back was a DELETE against admin_settings. An admin who turned intake notifications on could not turn them off. Whether empty is a mistake is a fact about the setting rather than about its type, so it is now declared on the setting, in the DEFINITIONS row that already carries its type and fallback. A new setting states it once, in the place someone adding one is already editing, and nothing else has to know. That is what makes this different from special-casing two names in the validator, which would have left the next such setting to rediscover the same bug. The blanket refusal stays the default, because for a setting with a non-empty fallback an empty value really is a mistake: an empty greeting format renders every greeting as nothing at all, which reads as a broken email rather than as something a person cleared. Both those cases keep their tests. Whitespace is normalised to empty rather than stored. Somebody clearing a field they cannot see the end of leaves spaces behind, and they meant cleared. The tests check that the clearing survives the request rather than only being echoed back — the last one sets a value, clears it, and then reads it again through GET, which is the assertion that would have caught this had it existed. Closes #280 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -95,6 +95,51 @@ describe('PUT /api/admin/settings', () => {
|
||||
expect(res.body.error).toContain(name);
|
||||
});
|
||||
|
||||
/**
|
||||
* The other half of the same question (#280).
|
||||
*
|
||||
* These two settings document empty as their default and as a working
|
||||
* configuration — no notification address, and no ceiling reset recorded. The
|
||||
* blanket "text cannot be empty" rule meant an address could be set and then
|
||||
* never removed through the admin at all, only by a DELETE against the table.
|
||||
*/
|
||||
it.each(['intakeNotifyEmail', 'intakeCeilingResetAt'])(
|
||||
'lets %s be cleared, because empty is its documented default',
|
||||
async (name) => {
|
||||
const set = await request(app)
|
||||
.put('/api/admin/settings')
|
||||
.send({ [name]: name === 'intakeNotifyEmail' ? 'alerts@example.com' : '2026-09-01T00:00:00Z' });
|
||||
expect(set.status).toBe(200);
|
||||
expect(set.body[name]).not.toBe('');
|
||||
|
||||
const cleared = await request(app).put('/api/admin/settings').send({ [name]: '' });
|
||||
|
||||
expect(cleared.status).toBe(200);
|
||||
expect(cleared.body[name]).toBe('');
|
||||
}
|
||||
);
|
||||
|
||||
// Whitespace is how a person clears a field they cannot see the end of, so it
|
||||
// means cleared rather than being stored as spaces.
|
||||
it('treats whitespace as cleared rather than storing it', async () => {
|
||||
await request(app).put('/api/admin/settings').send({ intakeNotifyEmail: 'alerts@example.com' });
|
||||
|
||||
const res = await request(app).put('/api/admin/settings').send({ intakeNotifyEmail: ' ' });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.body.intakeNotifyEmail).toBe('');
|
||||
});
|
||||
|
||||
// The clearing is real, not just echoed back in the response.
|
||||
it('reads a cleared setting back as empty on a later request', async () => {
|
||||
await request(app).put('/api/admin/settings').send({ intakeNotifyEmail: 'alerts@example.com' });
|
||||
await request(app).put('/api/admin/settings').send({ intakeNotifyEmail: '' });
|
||||
|
||||
const res = await request(app).get('/api/admin/settings');
|
||||
|
||||
expect(res.body.intakeNotifyEmail).toBe('');
|
||||
});
|
||||
|
||||
// Every field is validated before any is written, so a request that is part
|
||||
// nonsense does not half-apply.
|
||||
it('does not write anything when one field in the request is invalid', async () => {
|
||||
|
||||
Reference in New Issue
Block a user