feat(spa): guard angular host/player api contracts
All checks were successful
CI / test-and-quality (push) Successful in 2m22s
CI / test-and-quality (pull_request) Successful in 1m56s

This commit is contained in:
2026-03-01 16:40:34 +00:00
parent 9a69110c7d
commit fb782432ea
4 changed files with 527 additions and 3 deletions

View File

@@ -206,6 +206,126 @@ describe('createAngularApiClient', () => {
}
});
it('maps host/player gameplay endpoints through typed response mappers', async () => {
const get = vi.fn<AngularHttpClientLike['get']>(async <T>(url: string) => {
if (url === '/lobby/sessions/ABCD12/scoreboard') {
return {
session: { code: 'ABCD12', status: 'reveal', current_round: 1 },
leaderboard: [
{ id: 2, nickname: 'Maja', score: 11 },
{ id: 3, nickname: 'Bo', score: 7 }
]
} as T;
}
throw { status: 404, error: { error: 'Not found' } };
});
const post = vi.fn<AngularHttpClientLike['post']>(async <T>(url: string, body: unknown) => {
if (url === '/lobby/sessions/ABCD12/questions/show') {
expect(body).toEqual({});
return {
round_question: {
id: 77,
prompt: 'Prompt?',
round_number: 1,
shown_at: '2026-03-01T16:00:00Z',
lie_deadline_at: '2026-03-01T16:00:30Z'
},
config: { lie_seconds: 30 }
} as T;
}
if (url === '/lobby/sessions/ABCD12/questions/77/answers/mix') {
expect(body).toEqual({});
return {
session: { code: 'ABCD12', status: 'guess', current_round: 1 },
round_question: { id: 77, round_number: 1 },
answers: [{ text: 'A' }, { text: 'B' }]
} as T;
}
if (url === '/lobby/sessions/ABCD12/questions/77/scores/calculate') {
expect(body).toEqual({});
return {
session: { code: 'ABCD12', status: 'reveal', current_round: 1 },
round_question: { id: 77, round_number: 1 },
events_created: 3,
leaderboard: [{ id: 2, nickname: 'Maja', score: 11 }]
} as T;
}
if (url === '/lobby/sessions/ABCD12/rounds/next') {
expect(body).toEqual({});
return { session: { code: 'ABCD12', status: 'lobby', current_round: 2 } } as T;
}
if (url === '/lobby/sessions/ABCD12/finish') {
expect(body).toEqual({});
return {
session: { code: 'ABCD12', status: 'finished', current_round: 2 },
winner: { id: 2, nickname: 'Maja', score: 15 },
leaderboard: [{ id: 2, nickname: 'Maja', score: 15 }]
} as T;
}
if (url === '/lobby/sessions/ABCD12/questions/77/lies/submit') {
expect(body).toEqual({ player_id: 9, session_token: 'tok', text: 'my lie' });
return {
lie: {
id: 100,
player_id: 9,
round_question_id: 77,
text: 'my lie',
created_at: '2026-03-01T16:00:10Z'
},
window: { lie_deadline_at: '2026-03-01T16:00:30Z' }
} as T;
}
if (url === '/lobby/sessions/ABCD12/questions/77/guesses/submit') {
expect(body).toEqual({ player_id: 9, session_token: 'tok', selected_text: 'A' });
return {
guess: {
id: 200,
player_id: 9,
round_question_id: 77,
selected_text: 'A',
is_correct: false,
fooled_player_id: 3,
created_at: '2026-03-01T16:01:00Z'
},
window: { guess_deadline_at: '2026-03-01T16:01:30Z' }
} as T;
}
throw { status: 404, error: { error: 'Not found' } };
});
const client = createAngularApiClient({ get, post } as AngularHttpClientLike);
const showQuestion = await client.showQuestion('abcd12');
expect(showQuestion.ok).toBe(true);
const mixAnswers = await client.mixAnswers('abcd12', 77);
expect(mixAnswers.ok).toBe(true);
const calculateScores = await client.calculateScores('abcd12', 77);
expect(calculateScores.ok).toBe(true);
const scoreboard = await client.getScoreboard('abcd12');
expect(scoreboard.ok).toBe(true);
const nextRound = await client.startNextRound('abcd12');
expect(nextRound.ok).toBe(true);
const finish = await client.finishGame('abcd12');
expect(finish.ok).toBe(true);
const submitLie = await client.submitLie('abcd12', 77, { player_id: 9, session_token: 'tok', text: 'my lie' });
expect(submitLie.ok).toBe(true);
const submitGuess = await client.submitGuess('abcd12', 77, {
player_id: 9,
session_token: 'tok',
selected_text: 'A'
});
expect(submitGuess.ok).toBe(true);
});
it('maps HttpErrorResponse-style failures to ApiResult errors', async () => {
const http = {
get: vi.fn<AngularHttpClientLike['get']>(async () => {