diff --git a/package-lock.json b/package-lock.json index 4a1ae45..f5c7857 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,7 @@ "eslint": "^8.57.0", "husky": "^9.1.7", "jest": "^29.7.0", + "jest-fetch-mock": "^3.0.3", "jest-simple-dot-reporter": "^1.0.5", "lint-staged": "^15.2.11", "npm-run-all": "^4.1.5", @@ -7348,6 +7349,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -10347,6 +10358,17 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-fetch-mock": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", + "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-fetch": "^3.0.4", + "promise-polyfill": "^8.1.3" + } + }, "node_modules/jest-get-type": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", @@ -12933,6 +12955,13 @@ "node": ">=0.4.0" } }, + "node_modules/promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "dev": true, + "license": "MIT" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", diff --git a/package.json b/package.json index 80d3b23..a3e4b18 100644 --- a/package.json +++ b/package.json @@ -248,6 +248,7 @@ "eslint": "^8.57.0", "husky": "^9.1.7", "jest": "^29.7.0", + "jest-fetch-mock": "^3.0.3", "jest-simple-dot-reporter": "^1.0.5", "lint-staged": "^15.2.11", "npm-run-all": "^4.1.5", diff --git a/src/api/providers/__tests__/unbound.test.ts b/src/api/providers/__tests__/unbound.test.ts new file mode 100644 index 0000000..721ba53 --- /dev/null +++ b/src/api/providers/__tests__/unbound.test.ts @@ -0,0 +1,64 @@ +import { UnboundHandler } from "../unbound" +import { ApiHandlerOptions } from "../../../shared/api" +import fetchMock from "jest-fetch-mock" + +fetchMock.enableMocks() + +describe("UnboundHandler", () => { + const mockOptions: ApiHandlerOptions = { + unboundApiKey: "test-api-key", + apiModelId: "test-model-id", + } + + beforeEach(() => { + fetchMock.resetMocks() + }) + + it("should initialize with options", () => { + const handler = new UnboundHandler(mockOptions) + expect(handler).toBeDefined() + }) + + it("should create a message successfully", async () => { + const handler = new UnboundHandler(mockOptions) + const mockResponse = { + choices: [{ message: { content: "Hello, world!" } }], + usage: { prompt_tokens: 5, completion_tokens: 7 }, + } + + fetchMock.mockResponseOnce(JSON.stringify(mockResponse)) + + const generator = handler.createMessage("system prompt", []) + const textResult = await generator.next() + const usageResult = await generator.next() + + expect(textResult.value).toEqual({ type: "text", text: "Hello, world!" }) + expect(usageResult.value).toEqual({ + type: "usage", + inputTokens: 5, + outputTokens: 7, + }) + }) + + it("should handle API errors", async () => { + const handler = new UnboundHandler(mockOptions) + fetchMock.mockResponseOnce(JSON.stringify({ error: "API error" }), { status: 400 }) + + const generator = handler.createMessage("system prompt", []) + await expect(generator.next()).rejects.toThrow("Unbound Gateway completion error: API error") + }) + + it("should handle network errors", async () => { + const handler = new UnboundHandler(mockOptions) + fetchMock.mockRejectOnce(new Error("Network error")) + + const generator = handler.createMessage("system prompt", []) + await expect(generator.next()).rejects.toThrow("Unbound Gateway completion error: Network error") + }) + + it("should return the correct model", () => { + const handler = new UnboundHandler(mockOptions) + const model = handler.getModel() + expect(model.id).toBe("gpt-4o") + }) +})