diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index b86f8dd..25f4554 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -7,11 +7,11 @@ ChatGPT MCP Server ## Status Planning complete. -Phase 0 complete. Phase 1 complete. Phase 2 in progress. +Phase 0 complete. Phase 1 complete. Phase 2 complete. ## Current Phase -Phase 2 - OpenAI Integration +Phase 3 — Not yet defined ## Completed Tasks @@ -23,11 +23,8 @@ Phase 2 - OpenAI Integration - Task 1.4 — Safe logging helper (`src/utils/logging.js`) ✅ - Task 2.1 — OpenAI client wrapper (`src/openai/client.js`) ✅ - Task 2.2 — Response builder (`src/openai/responses.js`) ✅ +- Task 2.3 — Error handling and edge cases for OpenAI integration (tests) ✅ ## Next Phase -Phase 2 - OpenAI Integration - -## Next Pending Task - -Task 2.3 — Error handling and edge cases for OpenAI integration +Phase 3 — Not yet defined diff --git a/TASKS.md b/TASKS.md index 07aab0e..ed3aea2 100644 --- a/TASKS.md +++ b/TASKS.md @@ -127,4 +127,8 @@ Requirements: - Mock OpenAI SDK network timeout errors separately from rate-limit errors. - Verify that no API keys or secrets are ever leaked in error messages across all tested paths. -Status: ⏳ Pending +Status: ✅ Complete + +--- + +Phase 2 complete. Phase 3 not yet defined. diff --git a/test/openai/responses.test.js b/test/openai/responses.test.js index d42747d..3c9c248 100644 --- a/test/openai/responses.test.js +++ b/test/openai/responses.test.js @@ -30,6 +30,18 @@ function makeNetworkError() { return err; } +function makeServerError() { + const err = new Error("Service unavailable"); + err.status = 503; + return err; +} + +function makeTimeoutError() { + const err = new Error("timeout"); + err.code = "ETIMEDOUT"; + return err; +} + // --- Import --- const { sendOpenAIResponse } = await import("../../src/openai/responses.js"); @@ -167,4 +179,38 @@ describe("sendOpenAIResponse", () => { sendOpenAIResponse(mockClient, { input: ["x"], model: "m", maxOutputTokens: -5 }) ).rejects.toHaveProperty("kind", "ValidationError"); }); + + it("throws ValidationError when maxOutputTokens is zero", async () => { + await expect( + sendOpenAIResponse(mockClient, { input: ["x"], model: "m", maxOutputTokens: 0 }) + ).rejects.toHaveProperty("kind", "ValidationError"); + }); + + it("throws ValidationError when maxOutputTokens is a non-positive float", async () => { + await expect( + sendOpenAIResponse(mockClient, { input: ["x"], model: "m", maxOutputTokens: 0.5 }) + ).rejects.toHaveProperty("kind", "ValidationError"); + }); + + it("throws ValidationError when maxOutputTokens is NaN", async () => { + await expect( + sendOpenAIResponse(mockClient, { input: ["x"], model: "m", maxOutputTokens: NaN }) + ).rejects.toHaveProperty("kind", "ValidationError"); + }); + + // ===== Error mapping edge cases (2) ===== + + it("maps status 503 to OpenAIRequestError", async () => { + mockCreate.mockRejectedValueOnce(makeServerError()); + await expect( + sendOpenAIResponse(mockClient, { input: ["x"], model: "m" }) + ).rejects.toHaveProperty("kind", "OpenAIRequestError"); + }); + + it("maps timeout error (code ETIMEDOUT) to OpenAIRequestError", async () => { + mockCreate.mockRejectedValueOnce(makeTimeoutError()); + await expect( + sendOpenAIResponse(mockClient, { input: ["x"], model: "m" }) + ).rejects.toHaveProperty("kind", "OpenAIRequestError"); + }); });