jest.mock('@angular/common/http', () => ({ HttpClient: class {}, HttpErrorResponse: class {}, })); import { parseSolveError, messageForSolveError, formatDdHhMm, mergeSolveEventIntoSolvers, } from '../../frontend/src/app/features/challenges/challenges.pure'; describe('ChallengeModal — flag submission branches', () => { describe('parseSolveError + messageForSolveError', () => { it('maps FLAG_INCORRECT body to "Incorrect flag. Try again."', () => { const code = parseSolveError(400, { code: 'FLAG_INCORRECT' }); expect(code).toBe('incorrect'); expect(messageForSolveError(code)).toBe('Incorrect flag. Try again.'); }); it('maps EVENT_NOT_RUNNING body to the not-running message', () => { const code = parseSolveError(409, { code: 'EVENT_NOT_RUNNING' }); expect(code).toBe('not_running'); expect(messageForSolveError(code)).toBe( 'Submissions are only accepted while the event is running.', ); }); it('maps status 401 to unauthorized message', () => { expect(messageForSolveError(parseSolveError(401, {}))).toBe( 'Your session has expired. Please sign in again.', ); }); it('maps status 403 to forbidden message', () => { expect(messageForSolveError(parseSolveError(403, {}))).toBe( 'You are not allowed to perform this action.', ); }); it('maps status 404 to not_found message', () => { expect(messageForSolveError(parseSolveError(404, {}))).toBe('Challenge not found.'); }); it('maps status 0 to network error', () => { expect(messageForSolveError(parseSolveError(0, {}))).toBe('Network error. Please try again.'); }); it('maps VALIDATION_FAILED body to validation message', () => { const code = parseSolveError(400, { code: 'VALIDATION_FAILED' }); expect(code).toBe('validation'); expect(messageForSolveError(code)).toBe('Invalid submission.'); }); it('falls back to "unknown" for unrecognized errors', () => { expect(messageForSolveError(parseSolveError(500, {}))).toBe( 'Unexpected error. Please try again.', ); }); it('renders the already_solved message for idempotent re-submit', () => { expect(messageForSolveError('already_solved')).toBe('You already solved this challenge.'); }); }); describe('rejected-then-accepted flow sequencing', () => { it('orders messages so a wrong flag surfaces "Incorrect" and a successful submit surfaces "Awarded"', () => { const wrong = messageForSolveError(parseSolveError(400, { code: 'FLAG_INCORRECT' })); expect(wrong).toBe('Incorrect flag. Try again.'); const awarded = formatDdHhMm(3600); expect(awarded).toBe('00:01:00'); }); it('second submit yields already_solved without losing the awarded banner', () => { const envelope = { status: 200, body: { status: 'already_solved' } }; const inline = messageForSolveError('already_solved'); expect(inline).toBe('You already solved this challenge.'); expect(envelope.status).toBe(200); }); }); describe('live solve merge on the modal solvers list', () => { it('keeps existing solvers sorted by solvedAtUtc and assigns positions in arrival order', () => { const merged = mergeSolveEventIntoSolvers( [ { position: 1, playerId: 'alice', playerName: 'alice', solvedAtUtc: '2025-01-01T00:00:00.000Z', awardedPoints: 100, basePoints: 100, rankBonus: 0, isFirst: true, isSecond: false, isThird: false, }, ], { challengeId: 'c1', playerId: 'bob', playerName: 'bob', awardedPoints: 50, rankBonus: 0, awardedAtUtc: '2025-01-02T00:00:00.000Z', position: 2, }, ); expect(merged.map((r) => r.playerName)).toEqual(['alice', 'bob']); expect(merged.map((r) => r.position)).toEqual([1, 2]); }); it('does not double-insert when the same (playerId, solvedAtUtc) is replayed', () => { const seed = [ { position: 1, playerId: 'alice', playerName: 'alice', solvedAtUtc: '2025-01-01T00:00:00.000Z', awardedPoints: 100, basePoints: 100, rankBonus: 0, isFirst: true, isSecond: false, isThird: false, }, ]; const dup = mergeSolveEventIntoSolvers(seed, { challengeId: 'c1', playerId: 'alice', playerName: 'alice', awardedPoints: 100, rankBonus: 0, awardedAtUtc: '2025-01-01T00:00:00.000Z', position: 1, }); expect(dup).toHaveLength(1); }); it('marks gold/silver/bronze on the merged row based on the SSE position', () => { const merged = mergeSolveEventIntoSolvers([], { challengeId: 'c1', playerId: 'gold', playerName: 'gold', awardedPoints: 100, rankBonus: 50, awardedAtUtc: '2025-01-01T00:00:00.000Z', position: 1, }); expect(merged[0].isFirst).toBe(true); expect(merged[0].isSecond).toBe(false); expect(merged[0].isThird).toBe(false); expect(merged[0].rankBonus).toBe(50); }); }); });