AI Implementation feature(891): Admin Area General Settings and Categories 1.09 (#31)

This commit was merged in pull request #31.
This commit is contained in:
2026-07-22 15:47:04 +00:00
parent 2a6182b938
commit 4656a5b082
11 changed files with 432 additions and 42 deletions
@@ -161,6 +161,65 @@ describe('GeneralSettingsSchema - validation rules', () => {
});
});
describe('GeneralSettingsSchema - defaultChallengeIp (Job 891)', () => {
const base = {
pageTitle: 'T',
logo: '',
welcomeMarkdown: '',
themeKey: 'classic',
eventStartUtc: '2026-01-01T00:00:00Z',
eventEndUtc: '2026-02-01T00:00:00Z',
registrationsEnabled: false,
};
it('accepts a valid IPv4 address', () => {
const r = GeneralSettingsSchema.safeParse({ ...base, defaultChallengeIp: '10.66.77.88' });
expect(r.success).toBe(true);
if (r.success) expect(r.data.defaultChallengeIp).toBe('10.66.77.88');
});
it('accepts a valid hostname', () => {
const r = GeneralSettingsSchema.safeParse({ ...base, defaultChallengeIp: 'challenge.example.com' });
expect(r.success).toBe(true);
if (r.success) expect(r.data.defaultChallengeIp).toBe('challenge.example.com');
});
it('trims surrounding whitespace from a valid value', () => {
const r = GeneralSettingsSchema.safeParse({ ...base, defaultChallengeIp: ' 10.20.30.40 ' });
expect(r.success).toBe(true);
if (r.success) expect(r.data.defaultChallengeIp).toBe('10.20.30.40');
});
it('rejects an empty string', () => {
const r = GeneralSettingsSchema.safeParse({ ...base, defaultChallengeIp: '' });
expect(r.success).toBe(false);
if (r.success) return;
expect(r.error.issues.some((i) => i.path.join('.') === 'defaultChallengeIp')).toBe(true);
expect(r.error.issues.some((i) => /required/i.test(i.message))).toBe(true);
});
it('rejects a whitespace-only value', () => {
const r = GeneralSettingsSchema.safeParse({ ...base, defaultChallengeIp: ' ' });
expect(r.success).toBe(false);
if (r.success) return;
expect(r.error.issues.some((i) => i.path.join('.') === 'defaultChallengeIp')).toBe(true);
});
it('rejects incomplete IPv4 forms', () => {
const r1 = GeneralSettingsSchema.safeParse({ ...base, defaultChallengeIp: '10.0.0.' });
const r2 = GeneralSettingsSchema.safeParse({ ...base, defaultChallengeIp: '10.0.0' });
expect(r1.success).toBe(false);
expect(r2.success).toBe(false);
});
it('rejects malformed addresses with the format message', () => {
const r = GeneralSettingsSchema.safeParse({ ...base, defaultChallengeIp: '999.1.1.1' });
expect(r.success).toBe(false);
if (r.success) return;
expect(r.error.issues.some((i) => /IPv4/.test(i.message) || /hostname/.test(i.message))).toBe(true);
});
});
describe('AdminGeneralService.updateSettings - happy path', () => {
it('persists all keys and emits a settings event', async () => {
const stored: Record<string, string> = {};
+88
View File
@@ -1,9 +1,13 @@
import {
datetimeMessage,
defaultChallengeAddressError,
defaultChallengeAddressMessage,
deriveEventState,
endAfterStartValidator,
eventEndFieldMessage,
isValidDefaultChallengeAddress,
isoDatetimeValidator,
normalizeDefaultChallengeAddress,
normalizePageTitle,
pageTitleError,
pageTitleMessage,
@@ -238,3 +242,87 @@ describe('eventEndFieldMessage (Job 889)', () => {
expect(eventEndFieldMessage({}, {})).toBeNull();
});
});
describe('isValidDefaultChallengeAddress (Job 891)', () => {
it('accepts valid IPv4 addresses', () => {
expect(isValidDefaultChallengeAddress('10.66.77.88')).toBe(true);
expect(isValidDefaultChallengeAddress('127.0.0.1')).toBe(true);
expect(isValidDefaultChallengeAddress('0.0.0.0')).toBe(true);
expect(isValidDefaultChallengeAddress('255.255.255.255')).toBe(true);
});
it('accepts valid hostnames', () => {
expect(isValidDefaultChallengeAddress('challenge.example.com')).toBe(true);
expect(isValidDefaultChallengeAddress('ctf.example.org')).toBe(true);
});
it('rejects incomplete IPv4 forms', () => {
expect(isValidDefaultChallengeAddress('10.0.0.')).toBe(false);
expect(isValidDefaultChallengeAddress('10.0.0')).toBe(false);
expect(isValidDefaultChallengeAddress('10')).toBe(false);
});
it('rejects IPv4 addresses with out-of-range octets', () => {
expect(isValidDefaultChallengeAddress('999.1.1.1')).toBe(false);
expect(isValidDefaultChallengeAddress('256.0.0.1')).toBe(false);
});
it('rejects empty or whitespace-only strings', () => {
expect(isValidDefaultChallengeAddress('')).toBe(false);
expect(isValidDefaultChallengeAddress(' ')).toBe(false);
});
});
describe('defaultChallengeAddressError', () => {
it('returns "required" for empty or whitespace-only values', () => {
expect(defaultChallengeAddressError('')).toBe('required');
expect(defaultChallengeAddressError(' ')).toBe('required');
expect(defaultChallengeAddressError(null)).toBe('required');
});
it('returns "format" for malformed values', () => {
expect(defaultChallengeAddressError('10.0.0.')).toBe('format');
expect(defaultChallengeAddressError('10.0.0')).toBe('format');
expect(defaultChallengeAddressError('999.1.1.1')).toBe('format');
});
it('returns null for valid IPv4 or hostname values', () => {
expect(defaultChallengeAddressError('10.66.77.88')).toBeNull();
expect(defaultChallengeAddressError('challenge.example.com')).toBeNull();
});
});
describe('normalizeDefaultChallengeAddress', () => {
it('trims surrounding whitespace from valid input', () => {
expect(normalizeDefaultChallengeAddress(' 10.20.30.40 ')).toBe('10.20.30.40');
});
it('returns empty string for empty or whitespace-only input', () => {
expect(normalizeDefaultChallengeAddress('')).toBe('');
expect(normalizeDefaultChallengeAddress(' ')).toBe('');
});
});
describe('defaultChallengeAddressMessage', () => {
const requiredMsg = 'Default challenge IP is required and cannot contain only whitespace.';
const formatMsg = 'Default challenge IP must be a valid IPv4 address or hostname.';
it('returns the required message for empty or whitespace-only values', () => {
expect(defaultChallengeAddressMessage('', null)).toBe(requiredMsg);
expect(defaultChallengeAddressMessage(' ', null)).toBe(requiredMsg);
});
it('returns the format message for malformed values', () => {
expect(defaultChallengeAddressMessage('10.0.0.', null)).toBe(formatMsg);
expect(defaultChallengeAddressMessage('10.0.0', null)).toBe(formatMsg);
});
it('returns null for valid values', () => {
expect(defaultChallengeAddressMessage('10.66.77.88', null)).toBeNull();
expect(defaultChallengeAddressMessage('challenge.example.com', null)).toBeNull();
});
it('surfaces control-level errors when raw value would otherwise look valid', () => {
expect(defaultChallengeAddressMessage('not valid', { defaultChallengeAddressFormat: true })).toBe(formatMsg);
});
});