AI Implementation feature(902): Admin Area Challenges: List, Import/Export and Add/Edit Modal 1.02 (#43)

This commit was merged in pull request #43.
This commit is contained in:
2026-07-22 21:47:52 +00:00
parent d3307c3225
commit fff0a600df
8 changed files with 351 additions and 159 deletions
@@ -0,0 +1,83 @@
import '@angular/compiler';
import { FormBuilder } from '@angular/forms';
import {
buildChallengeFormGroup,
} from '../../frontend/src/app/features/admin/challenges/challenge-form-modal.component';
import {
CATEGORY_ID_PATTERN,
ChallengeFormDefaults,
firstErrorTab,
validateChallengeForm,
} from '../../frontend/src/app/features/admin/challenges/challenge-form.pure';
function makeDefaults(overrides: Partial<ChallengeFormDefaults> = {}): ChallengeFormDefaults {
return {
name: 'Missing Category Attempt',
descriptionMd: 'desc',
categoryId: '11111111-1111-4111-8111-111111111111',
difficulty: 'MEDIUM',
initialPoints: 100,
minimumPoints: 50,
decaySolves: 10,
flag: 'flag{X}',
protocol: 'WEB',
port: null,
ipAddress: '',
enabled: true,
...overrides,
};
}
const VALID_CATEGORY_ID = '11111111-1111-4111-8111-111111111111';
describe('Job 902: challenge form Category validation', () => {
it('CATEGORY_ID_PATTERN accepts a canonical UUID v4', () => {
expect(CATEGORY_ID_PATTERN.test(VALID_CATEGORY_ID)).toBe(true);
});
it('validateChallengeForm flags empty categoryId', () => {
const errs = validateChallengeForm(makeDefaults({ categoryId: '' }));
expect(errs.categoryId).toBe('Category is required');
});
it('validateChallengeForm flags whitespace-only categoryId', () => {
const errs = validateChallengeForm(makeDefaults({ categoryId: ' ' }));
expect(errs.categoryId).toBe('Category is required');
});
it('validateChallengeForm flags non-UUID categoryId (defense in depth)', () => {
const errs = validateChallengeForm(makeDefaults({ categoryId: 'not-a-uuid' }));
expect(errs.categoryId).toBe('Category is required');
});
it('validateChallengeForm accepts a well-formed UUID categoryId', () => {
const errs = validateChallengeForm(makeDefaults());
expect(errs.categoryId).toBeUndefined();
});
it('firstErrorTab routes a categoryId error to the general tab', () => {
expect(firstErrorTab({ categoryId: 'Category is required' })).toBe('general');
});
it('buildChallengeFormGroup applies a uuid validator on categoryId', () => {
const form = buildChallengeFormGroup(new FormBuilder());
form.controls.categoryId.setValue('not-a-uuid');
form.controls.categoryId.markAsTouched();
expect(form.controls.categoryId.errors?.['uuid']).toBe(true);
expect(form.controls.categoryId.errors?.['required']).toBeUndefined();
});
it('buildChallengeFormGroup uuid validator passes for a valid UUID v4', () => {
const form = buildChallengeFormGroup(new FormBuilder());
form.controls.categoryId.setValue(VALID_CATEGORY_ID);
form.controls.categoryId.markAsTouched();
expect(form.controls.categoryId.errors?.['uuid']).toBeUndefined();
});
it('buildChallengeFormGroup categoryId control still flags required for empty value', () => {
const form = buildChallengeFormGroup(new FormBuilder());
form.controls.categoryId.setValue('');
form.controls.categoryId.markAsTouched();
expect(form.controls.categoryId.errors?.['required']).toBe(true);
});
});
+2 -2
View File
@@ -27,7 +27,7 @@ function makeDetail(overrides: Partial<AdminChallengeDetail> = {}): AdminChallen
id: 'd1',
name: 'Sample',
descriptionMd: '# hi',
categoryId: 'cat-1',
categoryId: '11111111-1111-4111-8111-111111111111',
categoryAbbreviation: 'CRY',
categoryIconPath: '/uploads/icons/CRY.png',
difficulty: 'MEDIUM',
@@ -58,7 +58,7 @@ function makeDefaults(overrides: Partial<ChallengeFormDefaults> = {}): Challenge
return {
name: 'X',
descriptionMd: 'd',
categoryId: 'cat-1',
categoryId: '11111111-1111-4111-8111-111111111111',
difficulty: 'MEDIUM',
initialPoints: 100,
minimumPoints: 50,