Skip to main content
Glama
gemini.test.ts10.3 kB
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { GeminiImageClient } from "./gemini.js"; describe("GeminiImageClient", () => { const mockApiKey = "test-api-key"; beforeEach(() => { vi.stubGlobal("fetch", vi.fn()); }); afterEach(() => { vi.unstubAllGlobals(); }); describe("constructor", () => { it("should create client with valid API key", () => { const client = new GeminiImageClient(mockApiKey); expect(client).toBeInstanceOf(GeminiImageClient); }); it("should throw error when API key is empty", () => { expect(() => new GeminiImageClient("")).toThrow("GEMINI_API_KEY is required"); }); }); describe("generateImage", () => { it("should generate image with default options", async () => { const mockResponse = { candidates: [ { content: { parts: [ { text: "A beautiful sunset" }, { inlineData: { mimeType: "image/png", data: "base64encodedimage", }, }, ], }, }, ], }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const client = new GeminiImageClient(mockApiKey); const result = await client.generateImage({ prompt: "a sunset" }); expect(result).toEqual({ mimeType: "image/png", base64Data: "base64encodedimage", description: "A beautiful sunset", }); expect(fetch).toHaveBeenCalledWith( expect.stringContaining("gemini-3-pro-image-preview:generateContent"), expect.objectContaining({ method: "POST", headers: { "Content-Type": "application/json" }, }) ); }); it("should pass aspect ratio and image size to API for image models", async () => { const mockResponse = { candidates: [ { content: { parts: [ { inlineData: { mimeType: "image/png", data: "base64data", }, }, ], }, }, ], }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const client = new GeminiImageClient(mockApiKey); await client.generateImage({ prompt: "a cat", aspectRatio: "16:9", imageSize: "4K", model: "gemini-3-pro-image-preview", // Use image model to test imageConfig }); const callArgs = vi.mocked(fetch).mock.calls[0]; const body = JSON.parse(callArgs[1]?.body as string); expect(body.generationConfig.imageConfig).toEqual({ aspectRatio: "16:9", imageSize: "4K", }); }); it("should not include imageConfig for non-image models", async () => { const mockResponse = { candidates: [ { content: { parts: [ { inlineData: { mimeType: "image/png", data: "base64data", }, }, ], }, }, ], }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const client = new GeminiImageClient(mockApiKey); await client.generateImage({ prompt: "a cat", aspectRatio: "16:9", // These should be ignored imageSize: "4K", model: "gemini-2.0-flash-exp", }); const callArgs = vi.mocked(fetch).mock.calls[0]; const body = JSON.parse(callArgs[1]?.body as string); expect(body.generationConfig.imageConfig).toBeUndefined(); }); it("should reject invalid model names", async () => { const client = new GeminiImageClient(mockApiKey); await expect( client.generateImage({ prompt: "test", model: "../../malicious-path", }) ).rejects.toThrow("Invalid model"); }); it("should throw error on API failure", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: false, status: 401, text: () => Promise.resolve("Unauthorized"), } as Response); const client = new GeminiImageClient(mockApiKey); await expect(client.generateImage({ prompt: "test" })).rejects.toThrow( "Gemini API error (401): Unauthorized" ); }); it("should throw error when API returns error object", async () => { const mockResponse = { error: { code: 400, message: "Invalid request", status: "INVALID_ARGUMENT", }, }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const client = new GeminiImageClient(mockApiKey); await expect(client.generateImage({ prompt: "test" })).rejects.toThrow( "Gemini API error: Invalid request" ); }); it("should throw error when no candidates returned", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve({ candidates: [] }), } as Response); const client = new GeminiImageClient(mockApiKey); await expect(client.generateImage({ prompt: "test" })).rejects.toThrow( "No image generated - empty response from Gemini" ); }); it("should throw error when no image data in response", async () => { const mockResponse = { candidates: [ { content: { parts: [{ text: "Just text, no image" }], }, }, ], }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const client = new GeminiImageClient(mockApiKey); await expect(client.generateImage({ prompt: "test" })).rejects.toThrow( "No image data in Gemini response" ); }); it("should handle response without description", async () => { const mockResponse = { candidates: [ { content: { parts: [ { inlineData: { mimeType: "image/jpeg", data: "imagedata", }, }, ], }, }, ], }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const client = new GeminiImageClient(mockApiKey); const result = await client.generateImage({ prompt: "test" }); expect(result).toEqual({ mimeType: "image/jpeg", base64Data: "imagedata", description: undefined, }); }); }); describe("describeImage", () => { it("should describe image with default prompt", async () => { const mockResponse = { candidates: [ { content: { parts: [{ text: "A beautiful sunset over mountains" }], }, }, ], }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const client = new GeminiImageClient(mockApiKey); const result = await client.describeImage({ images: [{ data: "base64data", mimeType: "image/png" }], }); expect(result).toBe("A beautiful sunset over mountains"); const callArgs = vi.mocked(fetch).mock.calls[0]; const body = JSON.parse(callArgs[1]?.body as string); expect(body.generationConfig.responseModalities).toEqual(["TEXT"]); }); it("should describe image with custom prompt", async () => { const mockResponse = { candidates: [ { content: { parts: [{ text: "There are 5 people in this image" }], }, }, ], }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const client = new GeminiImageClient(mockApiKey); const result = await client.describeImage({ images: [{ data: "base64data", mimeType: "image/png" }], prompt: "How many people are in this image?", }); expect(result).toBe("There are 5 people in this image"); const callArgs = vi.mocked(fetch).mock.calls[0]; const body = JSON.parse(callArgs[1]?.body as string); expect(body.contents[0].parts[0].text).toBe("How many people are in this image?"); }); it("should throw error when no images provided", async () => { const client = new GeminiImageClient(mockApiKey); await expect( client.describeImage({ images: [] }) ).rejects.toThrow("At least one image is required"); }); it("should throw error on API failure", async () => { vi.mocked(fetch).mockResolvedValueOnce({ ok: false, status: 500, text: () => Promise.resolve("Server Error"), } as Response); const client = new GeminiImageClient(mockApiKey); await expect( client.describeImage({ images: [{ data: "base64data", mimeType: "image/png" }], }) ).rejects.toThrow("Gemini API error (500)"); }); it("should throw error when no description returned", async () => { const mockResponse = { candidates: [ { content: { parts: [], }, }, ], }; vi.mocked(fetch).mockResolvedValueOnce({ ok: true, json: () => Promise.resolve(mockResponse), } as Response); const client = new GeminiImageClient(mockApiKey); await expect( client.describeImage({ images: [{ data: "base64data", mimeType: "image/png" }], }) ).rejects.toThrow("No description in Gemini response"); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mrafaeldie12/nano-banana-pro-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server