diff --git a/orchestrator/src/server/services/rxresume-client.test.ts b/orchestrator/src/server/services/rxresume-client.test.ts index a5aee54..872f966 100644 --- a/orchestrator/src/server/services/rxresume-client.test.ts +++ b/orchestrator/src/server/services/rxresume-client.test.ts @@ -260,6 +260,43 @@ describe('RxResumeClient', () => { expect(token).toBe('alt-token-field'); }); + it('extracts token from set-cookie header when missing from body', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: vi.fn().mockReturnValue(null), + getSetCookie: vi + .fn() + .mockReturnValue(['Authentication=cookie-token; Path=/; HttpOnly']), + }, + json: async () => ({}), + }); + vi.stubGlobal('fetch', mockFetch); + + const token = await client.login('test@example.com', 'password123'); + + expect(token).toBe('cookie-token'); + }); + + it('extracts token from set-cookie string header fallback', async () => { + const mockFetch = vi.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: vi + .fn() + .mockReturnValue('Authentication=string-token; Path=/; HttpOnly'), + }, + json: async () => ({}), + }); + vi.stubGlobal('fetch', mockFetch); + + const token = await client.login('test@example.com', 'password123'); + + expect(token).toBe('string-token'); + }); + it('throws error on login failure', async () => { const mockFetch = vi.fn().mockResolvedValue({ ok: false, diff --git a/orchestrator/src/server/services/rxresume-v5.ts b/orchestrator/src/server/services/rxresume-v5.ts index b11b908..114b02d 100644 --- a/orchestrator/src/server/services/rxresume-v5.ts +++ b/orchestrator/src/server/services/rxresume-v5.ts @@ -34,8 +34,6 @@ async function executeWithKeyRetries(url: string, options: RequestInit): Promise ? rawApiKey.split(',').map(k => k.trim()) : [rawApiKey]; - let lastError: Error | null = null; - // Start from the last working key index for (let attempt = 0; attempt < apiKeys.length; attempt++) { const i = (lastWorkingKeyIndex + attempt) % apiKeys.length; @@ -74,9 +72,7 @@ async function executeWithKeyRetries(url: string, options: RequestInit): Promise } return response.text(); } catch (error) { - lastError = error as Error; - - // If it was already handled by the 401 check above, it won't reach here + // If it was already handled by the 401 check above, it won't reach here // because of the 'continue'. This catch is for network errors or unexpected throw. throw error; } @@ -94,7 +90,7 @@ async function executeWithKeyRetries(url: string, options: RequestInit): Promise `); } - throw lastError || new Error('All Reactive Resume API keys failed.'); + throw new Error('All Reactive Resume API keys failed.'); } /** diff --git a/orchestrator/src/shared/settings-schema.ts b/orchestrator/src/shared/settings-schema.ts index 5801642..3398421 100644 --- a/orchestrator/src/shared/settings-schema.ts +++ b/orchestrator/src/shared/settings-schema.ts @@ -1,7 +1,7 @@ import { z } from "zod"; export const resumeProjectsSchema = z.object({ - maxProjects: z.number().int().min(1).max(100), + maxProjects: z.number().int().min(0).max(100), lockedProjectIds: z.array(z.string().trim().min(1)).max(200), aiSelectableProjectIds: z.array(z.string().trim().min(1)).max(200), });