# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: api/race-api.spec.ts >> RACE API - Chat Completion Health Check >> responds with a valid assistant message within the latency limit
- Location: tests/api/race-api.spec.ts:72:7

# Error details

```
Error: Expected HTTP 200 but got 401

expect(received).toBe(expected) // Object.is equality

Expected: 200
Received: 401
```

# Test source

```ts
  5   |  *
  6   |  * Verifies that the RACE API (used by HelixTA HR) is reachable and returns
  7   |  * a well-formed chat-completion response, within an acceptable latency, and
  8   |  * that it rejects invalid credentials. Designed to run on a recurring cron
  9   |  * schedule (e.g. hourly); results feed scripts/send-report.js.
  10  |  */
  11  | 
  12  | // `||` (not `??`) so a blank value in .env falls back to the default too.
  13  | const RACE_API_URL = process.env.RACE_API_URL || 'https://raceai.studyineurope.xyz/v1/chat/completions';
  14  | const RACE_API_KEY = process.env.RACE_API_KEY;
  15  | const RACE_MODEL = process.env.RACE_MODEL || 'gpt-oss-120b';
  16  | const MAX_LATENCY_MS = Number(process.env.RACE_MAX_LATENCY_MS) || 10_000;
  17  | const REQUEST_TIMEOUT_MS = 30_000;
  18  | 
  19  | // Strips any accidental occurrence of the API key from a string before it is
  20  | // logged, attached to the report, or thrown in an assertion message.
  21  | function redact(value: string): string {
  22  |   if (!RACE_API_KEY) return value;
  23  |   return value.split(RACE_API_KEY).join('***REDACTED***');
  24  | }
  25  | 
  26  | const requestBody = {
  27  |   model: RACE_MODEL,
  28  |   messages: [{ role: 'user', content: 'hi' }],
  29  |   max_tokens: 50,
  30  | };
  31  | 
  32  | interface ApiResult {
  33  |   status: number;
  34  |   bodyText: string;
  35  |   durationMs: number;
  36  | }
  37  | 
  38  | async function callRace(api: APIRequestContext, apiKey: string): Promise<ApiResult> {
  39  |   const start = Date.now();
  40  |   const response = await api.post(RACE_API_URL, {
  41  |     headers: { 'x-api-key': apiKey, 'Content-Type': 'application/json' },
  42  |     data: requestBody,
  43  |     timeout: REQUEST_TIMEOUT_MS,
  44  |   });
  45  |   return { status: response.status(), bodyText: await response.text(), durationMs: Date.now() - start };
  46  | }
  47  | 
  48  | async function attachContext(testInfo: TestInfo, result: Partial<ApiResult>) {
  49  |   await testInfo.attach('race-api-context', {
  50  |     body: JSON.stringify({ url: RACE_API_URL, model: RACE_MODEL, status: result.status ?? -1, durationMs: result.durationMs }, null, 2),
  51  |     contentType: 'application/json',
  52  |   });
  53  |   await testInfo.attach('race-api-response-body', {
  54  |     body: redact(result.bodyText || '<no response body captured>'),
  55  |     contentType: 'text/plain',
  56  |   });
  57  | }
  58  | 
  59  | test.describe('RACE API - Chat Completion Health Check', () => {
  60  |   let apiContext: APIRequestContext;
  61  | 
  62  |   test.beforeAll(async () => {
  63  |     // Fail loudly (never skip) so a misconfigured server cannot report a false pass.
  64  |     expect(RACE_API_KEY, 'RACE_API_KEY environment variable is not set').toBeTruthy();
  65  |     apiContext = await request.newContext();
  66  |   });
  67  | 
  68  |   test.afterAll(async () => {
  69  |     await apiContext?.dispose();
  70  |   });
  71  | 
  72  |   test('responds with a valid assistant message within the latency limit', async ({}, testInfo) => {
  73  |     const result: Partial<ApiResult> = {};
  74  |     try {
  75  |       Object.assign(result, await callRace(apiContext, RACE_API_KEY as string));
  76  |       console.log(`[RACE API] status=${result.status} durationMs=${result.durationMs}`);
  77  | 
  78  |       expect(result.status, `Expected HTTP 200 but got ${result.status}`).toBe(200);
  79  | 
  80  |       let json: any;
  81  |       try {
  82  |         json = JSON.parse(result.bodyText as string);
  83  |       } catch {
  84  |         throw new Error('Response body is not valid JSON');
  85  |       }
  86  | 
  87  |       expect(Array.isArray(json?.choices), 'Response should contain a "choices" array').toBe(true);
  88  |       expect(json.choices.length, 'Response "choices" array should not be empty').toBeGreaterThan(0);
  89  | 
  90  |       const message = json.choices[0]?.message;
  91  |       expect(message, 'First choice should contain a "message" object').toBeTruthy();
  92  |       expect(message.role, 'Assistant message should have role "assistant"').toBe('assistant');
  93  |       expect(typeof message.content, 'Assistant message content should be a string').toBe('string');
  94  |       expect(message.content.trim().length, 'Assistant message content should not be empty').toBeGreaterThan(0);
  95  | 
  96  |       expect(
  97  |         result.durationMs,
  98  |         `Response took ${result.durationMs}ms, limit is ${MAX_LATENCY_MS}ms`,
  99  |       ).toBeLessThanOrEqual(MAX_LATENCY_MS);
  100 | 
  101 |       console.log('[RACE API] PASS - received valid assistant response');
  102 |     } catch (error) {
  103 |       await attachContext(testInfo, result);
  104 |       console.log(`[RACE API] FAIL - status=${result.status ?? -1} durationMs=${result.durationMs ?? -1}`);
> 105 |       throw new Error(error instanceof Error ? redact(error.message) : String(error));
      |             ^ Error: Expected HTTP 200 but got 401
  106 |     }
  107 |   });
  108 | 
  109 |   test('rejects an invalid API key with 401/403', async ({}, testInfo) => {
  110 |     const result = await callRace(apiContext, 'invalid-key-for-negative-test');
  111 |     console.log(`[RACE API] negative test status=${result.status}`);
  112 |     if (![401, 403].includes(result.status)) await attachContext(testInfo, result);
  113 |     expect([401, 403], `Expected 401/403 for an invalid key but got ${result.status}`).toContain(result.status);
  114 |   });
  115 | });
  116 | 
```