import { HttpClient, HttpParams } from '@angular/common/http'; import { Injector, runInInjectionContext } from '@angular/core'; import { Observable } from 'rxjs'; import { ChallengesApiService } from '../../frontend/src/app/features/challenges/challenges.service'; import { ChallengeDetail } from '../../frontend/src/app/features/challenges/challenges.pure'; interface CapturedRequest { url: string; params: HttpParams; withCredentials: boolean; } function makeDetail(): ChallengeDetail { return { challenge: { id: 'c1', name: 'alpha', descriptionMd: '# Alpha', categoryId: 'cat1', categoryAbbreviation: 'CRY', categoryIconPath: '', difficulty: 'LOW', livePoints: 90, solveCount: 1, solvedByMe: false, }, solvers: [ { position: 1, playerId: 'p1', playerName: 'alice', solvedAtUtc: '2025-01-01T00:00:00.000Z', awardedPoints: 100, basePoints: 100, rankBonus: 0, isFirst: true, isSecond: false, isThird: false, }, ], }; } class StubHttpClient { public captured: CapturedRequest | null = null; get(url: string, options: { withCredentials?: boolean; params?: HttpParams | Record }): Observable { this.captured = { url, params: options.params instanceof HttpParams ? options.params : new HttpParams({ fromObject: (options.params ?? {}) as Record }), withCredentials: !!options.withCredentials, }; return new Observable((subscriber) => { subscriber.next(makeDetail() as unknown as T); subscriber.complete(); }); } post(_url: string, _body: unknown, _options: unknown): Observable { return new Observable(); } } describe('ChallengesApiService.getDetail', () => { let http: StubHttpClient; let service: ChallengesApiService; beforeEach(() => { http = new StubHttpClient(); const injector = Injector.create({ providers: [ { provide: HttpClient, useValue: http }, ChallengesApiService, ], }); service = runInInjectionContext(injector, () => injector.get(ChallengesApiService)); }); it('requests the challenge detail with include=solvers and withCredentials', (done) => { service.getDetail('c1').subscribe(() => { try { expect(http.captured).not.toBeNull(); expect(http.captured!.url).toBe('/api/v1/challenges/c1'); expect(http.captured!.params.get('include')).toBe('solvers'); expect(http.captured!.withCredentials).toBe(true); done(); } catch (err) { done(err); } }); }); it('encodes the challenge id in the URL path', (done) => { service.getDetail('c/with spaces').subscribe(() => { try { expect(http.captured!.url).toBe('/api/v1/challenges/c%2Fwith%20spaces'); expect(http.captured!.params.get('include')).toBe('solvers'); done(); } catch (err) { done(err); } }); }); });