feat: Admin Area Challenges: List, Import/Export and Add/Edit Modal 1.03
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
importErrorMessage,
|
||||
summarizeImportResult,
|
||||
} from '../../frontend/src/app/features/admin/challenges/challenges.pure';
|
||||
|
||||
describe('Job 903: import error + result helpers', () => {
|
||||
it('summarizeImportResult reports imported/skipped/warning counts', () => {
|
||||
const r = {
|
||||
imported: 2,
|
||||
skipped: [{ name: 'X', reason: 'Unknown category: Y' }],
|
||||
warnings: ['1 skipped.'],
|
||||
};
|
||||
const out = summarizeImportResult(r as any);
|
||||
expect(out).toContain('Imported 2');
|
||||
expect(out).toContain('Skipped 1');
|
||||
expect(out).toContain('1 warning');
|
||||
});
|
||||
|
||||
it('importErrorMessage prefers the nested error.message envelope', () => {
|
||||
expect(importErrorMessage({ error: { message: 'Server said no' } }, 'fallback')).toBe('Server said no');
|
||||
});
|
||||
|
||||
it('importErrorMessage falls back to err.message', () => {
|
||||
expect(importErrorMessage({ message: 'boom' }, 'fallback')).toBe('boom');
|
||||
});
|
||||
|
||||
it('importErrorMessage falls back to the supplied default when no message is available', () => {
|
||||
expect(importErrorMessage({}, 'fallback')).toBe('fallback');
|
||||
expect(importErrorMessage(null, 'fallback')).toBe('fallback');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Job 903: confirmed import handler contract', () => {
|
||||
function makeDoc() {
|
||||
return { format: 'hipctf-challenges', version: 1, exportedAt: '', challenges: [] };
|
||||
}
|
||||
|
||||
it('a successful confirm yields a closed-modal state and a status banner', async () => {
|
||||
const state = {
|
||||
importOpen: true as boolean,
|
||||
importDoc: makeDoc() as any,
|
||||
importPreview: { total: 2, conflicts: [], unknownCategories: [], warnings: [] } as any,
|
||||
importResult: null as any,
|
||||
importError: null as string | null,
|
||||
importing: false,
|
||||
statusMessage: null as string | null,
|
||||
};
|
||||
|
||||
// Simulate onImportConfirm body using pure helpers.
|
||||
try {
|
||||
const result = { imported: 2, skipped: [], warnings: [] };
|
||||
state.statusMessage = summarizeImportResult(result as any);
|
||||
await Promise.resolve();
|
||||
// Close-modal branch (the new behavior).
|
||||
state.importOpen = false;
|
||||
state.importDoc = null;
|
||||
state.importPreview = null;
|
||||
state.importResult = null;
|
||||
state.importError = null;
|
||||
} catch (err) {
|
||||
state.importError = importErrorMessage(err, 'Failed to import challenges');
|
||||
} finally {
|
||||
state.importing = false;
|
||||
}
|
||||
|
||||
expect(state.importOpen).toBe(false);
|
||||
expect(state.importDoc).toBeNull();
|
||||
expect(state.importPreview).toBeNull();
|
||||
expect(state.importResult).toBeNull();
|
||||
expect(state.importError).toBeNull();
|
||||
expect(state.statusMessage).toContain('Imported 2');
|
||||
expect(state.importing).toBe(false);
|
||||
});
|
||||
|
||||
it('a rejected confirm keeps the modal open and surfaces the error message', async () => {
|
||||
const state = {
|
||||
importOpen: true as boolean,
|
||||
importDoc: makeDoc() as any,
|
||||
importPreview: { total: 1, conflicts: [], unknownCategories: [], warnings: [] } as any,
|
||||
importResult: null as any,
|
||||
importError: null as string | null,
|
||||
importing: false,
|
||||
statusMessage: null as string | null,
|
||||
};
|
||||
|
||||
try {
|
||||
throw { error: { message: 'Server said no' } };
|
||||
} catch (err) {
|
||||
state.importError = importErrorMessage(err, 'Failed to import challenges');
|
||||
} finally {
|
||||
state.importing = false;
|
||||
}
|
||||
|
||||
expect(state.importOpen).toBe(true);
|
||||
expect(state.importError).toBe('Server said no');
|
||||
expect(state.importing).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user