AI Implementation feature(887): Admin Area General Settings and Categories 1.05 (#27)
This commit was merged in pull request #27.
This commit is contained in:
@@ -87,6 +87,73 @@ describe('GeneralSettingsSchema - validation rules', () => {
|
||||
expect(r.data.pageTitle).toBe('OpenVelo');
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects a non-datetime eventStartUtc ("not-a-date")', () => {
|
||||
const r = GeneralSettingsSchema.safeParse({
|
||||
pageTitle: 'T',
|
||||
logo: '',
|
||||
welcomeMarkdown: '',
|
||||
themeKey: 'classic',
|
||||
eventStartUtc: 'not-a-date',
|
||||
eventEndUtc: '2026-02-01T00:00:00Z',
|
||||
defaultChallengeIp: '127.0.0.1',
|
||||
registrationsEnabled: false,
|
||||
});
|
||||
expect(r.success).toBe(false);
|
||||
if (!r.success) {
|
||||
const paths = r.error.issues.map((i) => i.path.join('.'));
|
||||
expect(paths).toContain('eventStartUtc');
|
||||
expect(r.error.issues.some((i) => /datetime/i.test(i.message))).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects a non-datetime eventEndUtc and reports the issue on eventEndUtc', () => {
|
||||
const r = GeneralSettingsSchema.safeParse({
|
||||
pageTitle: 'T',
|
||||
logo: '',
|
||||
welcomeMarkdown: '',
|
||||
themeKey: 'classic',
|
||||
eventStartUtc: '2026-08-15T10:30:00.000Z',
|
||||
eventEndUtc: 'also-not-a-date',
|
||||
defaultChallengeIp: '127.0.0.1',
|
||||
registrationsEnabled: false,
|
||||
});
|
||||
expect(r.success).toBe(false);
|
||||
if (!r.success) {
|
||||
const paths = r.error.issues.map((i) => i.path.join('.'));
|
||||
expect(paths).toContain('eventEndUtc');
|
||||
expect(paths).not.toContain('eventStartUtc');
|
||||
expect(r.error.issues.some((i) => /datetime/i.test(i.message))).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects both fields as non-datetime strings (the Job negative case)', () => {
|
||||
const r = GeneralSettingsSchema.safeParse({
|
||||
pageTitle: 'T',
|
||||
logo: '',
|
||||
welcomeMarkdown: '',
|
||||
themeKey: 'classic',
|
||||
eventStartUtc: 'not-a-date',
|
||||
eventEndUtc: 'also-not-a-date',
|
||||
defaultChallengeIp: '127.0.0.1',
|
||||
registrationsEnabled: false,
|
||||
});
|
||||
expect(r.success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts empty strings (unconfigured event window) for both datetime fields', () => {
|
||||
const r = GeneralSettingsSchema.safeParse({
|
||||
pageTitle: 'T',
|
||||
logo: '',
|
||||
welcomeMarkdown: '',
|
||||
themeKey: 'classic',
|
||||
eventStartUtc: '',
|
||||
eventEndUtc: '',
|
||||
defaultChallengeIp: '127.0.0.1',
|
||||
registrationsEnabled: false,
|
||||
});
|
||||
expect(r.success).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('AdminGeneralService.updateSettings - happy path', () => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import {
|
||||
datetimeMessage,
|
||||
deriveEventState,
|
||||
endAfterStartValidator,
|
||||
isoDatetimeValidator,
|
||||
normalizePageTitle,
|
||||
pageTitleError,
|
||||
pageTitleMessage,
|
||||
@@ -161,3 +163,43 @@ describe('pageTitleMessage', () => {
|
||||
expect(pageTitleMessage('a'.repeat(121), { maxlength: { requiredLength: 120 } })).toBe(maxLengthMsg);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isoDatetimeValidator', () => {
|
||||
it('returns null for an empty value (unconfigured is allowed)', () => {
|
||||
expect(isoDatetimeValidator({ value: '' })).toBeNull();
|
||||
expect(isoDatetimeValidator({ value: null })).toBeNull();
|
||||
expect(isoDatetimeValidator({ value: undefined })).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for a valid ISO-8601 datetime', () => {
|
||||
expect(isoDatetimeValidator({ value: '2026-08-15T10:30:00.000Z' })).toBeNull();
|
||||
});
|
||||
|
||||
it('returns { invalidDatetime: true } for a non-datetime string', () => {
|
||||
expect(isoDatetimeValidator({ value: 'not-a-date' })).toEqual({ invalidDatetime: true });
|
||||
expect(isoDatetimeValidator({ value: 'also-not-a-date' })).toEqual({ invalidDatetime: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('datetimeMessage', () => {
|
||||
it('returns the per-field error when controlErrors carries invalidDatetime', () => {
|
||||
expect(datetimeMessage('Event start', 'not-a-date', { invalidDatetime: true }))
|
||||
.toBe('Event start must be a valid ISO-8601 datetime.');
|
||||
expect(datetimeMessage('Event end', 'garbage', { invalidDatetime: true }))
|
||||
.toBe('Event end must be a valid ISO-8601 datetime.');
|
||||
});
|
||||
|
||||
it('falls back to detecting an unparseable value when controlErrors are absent', () => {
|
||||
expect(datetimeMessage('Event start', 'not-a-date', null))
|
||||
.toBe('Event start must be a valid ISO-8601 datetime.');
|
||||
});
|
||||
|
||||
it('returns null for an empty value', () => {
|
||||
expect(datetimeMessage('Event start', '', null)).toBeNull();
|
||||
expect(datetimeMessage('Event start', '', { invalidDatetime: false as any })).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for a valid ISO-8601 datetime', () => {
|
||||
expect(datetimeMessage('Event start', '2026-08-15T10:30:00.000Z', null)).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user