From 8d3df1f8509fa02988e60021e3a2c3c4c412535c Mon Sep 17 00:00:00 2001 From: DEV-bot Date: Mon, 2 Mar 2026 01:30:13 +0000 Subject: [PATCH] feat(frontend): add angular app-shell API client skeleton --- frontend/angular/src/app/app.config.ts | 10 +++- .../angular/src/app/wpp-api-client.spec.ts | 43 ++++++++++++++ frontend/angular/src/app/wpp-api-client.ts | 58 +++++++++++++++++++ 3 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 frontend/angular/src/app/wpp-api-client.spec.ts create mode 100644 frontend/angular/src/app/wpp-api-client.ts diff --git a/frontend/angular/src/app/app.config.ts b/frontend/angular/src/app/app.config.ts index c43eeaf..bc74aa5 100644 --- a/frontend/angular/src/app/app.config.ts +++ b/frontend/angular/src/app/app.config.ts @@ -1,7 +1,15 @@ import { ApplicationConfig } from '@angular/core'; import { provideRouter, withHashLocation } from '@angular/router'; + import { routes } from './app.routes'; +import { createWppApiClient, WPP_API_CLIENT } from './wpp-api-client'; export const appConfig: ApplicationConfig = { - providers: [provideRouter(routes, withHashLocation())], + providers: [ + provideRouter(routes, withHashLocation()), + { + provide: WPP_API_CLIENT, + useFactory: () => createWppApiClient(), + }, + ], }; diff --git a/frontend/angular/src/app/wpp-api-client.spec.ts b/frontend/angular/src/app/wpp-api-client.spec.ts new file mode 100644 index 0000000..45f21fa --- /dev/null +++ b/frontend/angular/src/app/wpp-api-client.spec.ts @@ -0,0 +1,43 @@ +import { describe, expect, it, vi } from 'vitest'; + +import { createWppApiClient } from './wpp-api-client'; + +function jsonResponse(status: number, body: unknown) { + return { + ok: status >= 200 && status < 300, + status, + json: vi.fn().mockResolvedValue(body), + } as unknown as Response; +} + +describe('WPP Angular API client skeleton', () => { + it('normalizes host/player API calls through fetch transport', async () => { + const fetchMock = vi + .fn() + .mockResolvedValueOnce(jsonResponse(200, { session: { code: 'ABCD12', status: 'lobby', host_id: 1, current_round: 1, players_count: 1 }, players: [], round_question: null, phase_view_model: { status: 'lobby', round_number: 1, players_count: 1, constraints: { min_players_to_start: 2, max_players_mvp: 8, min_players_reached: false, max_players_allowed: true }, host: { can_start_round: false, can_show_question: false, can_mix_answers: false, can_calculate_scores: false, can_reveal_scoreboard: false, can_start_next_round: false, can_finish_game: false }, player: { can_join: true, can_submit_lie: false, can_submit_guess: false, can_view_final_result: false } } })) + .mockResolvedValueOnce(jsonResponse(201, { player: { id: 1, nickname: 'Luna', session_token: 'tok', score: 0 }, session: { code: 'ABCD12', status: 'lobby' } })); + + const client = createWppApiClient(fetchMock); + + const session = await client.getSession(' abcd12 '); + const joined = await client.joinSession({ code: ' abcd12 ', nickname: ' Luna ' }); + + expect(session.ok).toBe(true); + expect(joined.ok).toBe(true); + + expect(fetchMock).toHaveBeenNthCalledWith( + 1, + '/lobby/sessions/ABCD12', + expect.objectContaining({ method: 'GET', credentials: 'same-origin' }) + ); + expect(fetchMock).toHaveBeenNthCalledWith( + 2, + '/lobby/sessions/join', + expect.objectContaining({ + method: 'POST', + credentials: 'same-origin', + body: JSON.stringify({ code: 'ABCD12', nickname: 'Luna' }), + }) + ); + }); +}); diff --git a/frontend/angular/src/app/wpp-api-client.ts b/frontend/angular/src/app/wpp-api-client.ts new file mode 100644 index 0000000..973f10d --- /dev/null +++ b/frontend/angular/src/app/wpp-api-client.ts @@ -0,0 +1,58 @@ +import { InjectionToken } from '@angular/core'; + +import { + createAngularApiClient, + type AngularApiClient, + type AngularHttpClientLike, +} from '../../../src/api/angular-client'; + +export const WPP_API_CLIENT = new InjectionToken('WPP_API_CLIENT'); + +export interface FetchLike { + (input: string, init?: RequestInit): Promise; +} + +export function createFetchHttpClient(fetchImpl: FetchLike): AngularHttpClientLike { + return { + async get(url: string): Promise { + const response = await fetchImpl(url, { + method: 'GET', + headers: { Accept: 'application/json' }, + credentials: 'same-origin', + }); + const payload = await response.json().catch(() => ({})); + if (!response.ok) { + throw { + status: response.status, + message: (payload as { error?: string }).error ?? `HTTP ${response.status}`, + error: payload, + }; + } + return payload as T; + }, + async post(url: string, body: unknown): Promise { + const response = await fetchImpl(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + credentials: 'same-origin', + }); + const payload = await response.json().catch(() => ({})); + if (!response.ok) { + throw { + status: response.status, + message: (payload as { error?: string }).error ?? `HTTP ${response.status}`, + error: payload, + }; + } + return payload as T; + }, + }; +} + +export function createWppApiClient(fetchImpl: FetchLike = fetch.bind(globalThis)): AngularApiClient { + return createAngularApiClient(createFetchHttpClient(fetchImpl)); +} -- 2.39.5