Adding some simple tests. (#54)
This commit is contained in:
parent
d9ad2a74ae
commit
0c9e1ef61b
File diff suppressed because it is too large
Load Diff
|
@ -14,6 +14,7 @@
|
|||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"typecheck": "tsc --noEmit --jsx react",
|
||||
"format": "prettier --write .",
|
||||
"preflight": "npm run format --workspaces --if-present && npm run lint --workspaces --if-present && npm run test --workspaces --if-present",
|
||||
"artifactregistry-login": "npx google-artifactregistry-auth"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -10,14 +10,15 @@
|
|||
"start": "node dist/gemini.js",
|
||||
"debug": "node --inspect-brk dist/gemini.js",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"format": "prettier --write ."
|
||||
"format": "prettier --write .",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@google/genai": "^0.8.0",
|
||||
"@gemini-code/server": "1.0.0",
|
||||
"@google/genai": "^0.8.0",
|
||||
"diff": "^7.0.0",
|
||||
"dotenv": "^16.4.7",
|
||||
"fast-glob": "^3.3.3",
|
||||
|
@ -34,7 +35,8 @@
|
|||
"@types/node": "^20.11.24",
|
||||
"@types/react": "^19.1.0",
|
||||
"@types/yargs": "^17.0.32",
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
import { describe, it, expect, vi, beforeEach, Mock } from 'vitest';
|
||||
import { GoogleGenAI, Type, Content } from '@google/genai';
|
||||
import { GeminiClient } from './gemini-client.js';
|
||||
import { Config } from '../config/config.js';
|
||||
|
||||
// Mock the entire @google/genai module
|
||||
vi.mock('@google/genai');
|
||||
|
||||
// Mock the Config class and its methods
|
||||
vi.mock('../config/config.js', () => {
|
||||
// The mock constructor should accept the arguments but not explicitly return an object.
|
||||
// vi.fn() will create a mock instance that inherits from the prototype.
|
||||
const MockConfig = vi.fn();
|
||||
// Methods are mocked on the prototype, so instances will inherit them.
|
||||
MockConfig.prototype.getApiKey = vi.fn(() => 'mock-api-key');
|
||||
MockConfig.prototype.getModel = vi.fn(() => 'mock-model');
|
||||
MockConfig.prototype.getTargetDir = vi.fn(() => 'mock-target-dir');
|
||||
return { Config: MockConfig };
|
||||
});
|
||||
|
||||
// Define a type for the mocked GoogleGenAI instance structure
|
||||
type MockGoogleGenAIType = {
|
||||
models: {
|
||||
generateContent: Mock;
|
||||
};
|
||||
chats: {
|
||||
create: Mock;
|
||||
};
|
||||
};
|
||||
|
||||
describe('GeminiClient', () => {
|
||||
// Use the specific types defined above
|
||||
let mockGenerateContent: MockGoogleGenAIType['models']['generateContent'];
|
||||
let mockGoogleGenAIInstance: MockGoogleGenAIType;
|
||||
let config: Config;
|
||||
let client: GeminiClient;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
|
||||
// Mock the generateContent method specifically
|
||||
mockGenerateContent = vi.fn();
|
||||
|
||||
// Mock the chainable structure ai.models.generateContent
|
||||
mockGoogleGenAIInstance = {
|
||||
models: {
|
||||
generateContent: mockGenerateContent,
|
||||
},
|
||||
chats: {
|
||||
create: vi.fn(), // Mock create as well
|
||||
},
|
||||
};
|
||||
|
||||
// Configure the mocked GoogleGenAI constructor to return our mock instance
|
||||
(GoogleGenAI as Mock).mockImplementation(() => mockGoogleGenAIInstance);
|
||||
|
||||
config = new Config('mock-api-key-arg', 'mock-model-arg', 'mock-dir-arg');
|
||||
client = new GeminiClient(config);
|
||||
});
|
||||
|
||||
describe('generateJson', () => {
|
||||
it('should call ai.models.generateContent with correct parameters', async () => {
|
||||
const mockContents: Content[] = [
|
||||
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||
];
|
||||
const mockSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: { key: { type: Type.STRING } },
|
||||
};
|
||||
const mockApiResponse = { text: JSON.stringify({ key: 'value' }) };
|
||||
|
||||
mockGenerateContent.mockResolvedValue(mockApiResponse);
|
||||
await client.generateJson(mockContents, mockSchema);
|
||||
|
||||
expect(mockGenerateContent).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Use expect.objectContaining for the config assertion
|
||||
const expectedConfigMatcher = expect.objectContaining({
|
||||
temperature: 0,
|
||||
topP: 1,
|
||||
systemInstruction: expect.any(String),
|
||||
responseSchema: mockSchema,
|
||||
responseMimeType: 'application/json',
|
||||
});
|
||||
expect(mockGenerateContent).toHaveBeenCalledWith({
|
||||
model: 'mock-model',
|
||||
config: expectedConfigMatcher,
|
||||
contents: mockContents,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the parsed JSON response', async () => {
|
||||
const mockContents: Content[] = [
|
||||
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||
];
|
||||
const mockSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: { key: { type: Type.STRING } },
|
||||
};
|
||||
const expectedJson = { key: 'value' };
|
||||
const mockApiResponse = { text: JSON.stringify(expectedJson) };
|
||||
|
||||
mockGenerateContent.mockResolvedValue(mockApiResponse);
|
||||
|
||||
const result = await client.generateJson(mockContents, mockSchema);
|
||||
|
||||
expect(result).toEqual(expectedJson);
|
||||
});
|
||||
|
||||
it('should throw an error if API returns empty response', async () => {
|
||||
const mockContents: Content[] = [
|
||||
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||
];
|
||||
const mockSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: { key: { type: Type.STRING } },
|
||||
};
|
||||
const mockApiResponse = { text: '' }; // Empty response
|
||||
|
||||
mockGenerateContent.mockResolvedValue(mockApiResponse);
|
||||
|
||||
await expect(
|
||||
client.generateJson(mockContents, mockSchema),
|
||||
).rejects.toThrow(
|
||||
'Failed to generate JSON content: API returned an empty response.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if API response is not valid JSON', async () => {
|
||||
const mockContents: Content[] = [
|
||||
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||
];
|
||||
const mockSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: { key: { type: Type.STRING } },
|
||||
};
|
||||
const mockApiResponse = { text: 'invalid json' }; // Invalid JSON
|
||||
|
||||
mockGenerateContent.mockResolvedValue(mockApiResponse);
|
||||
|
||||
await expect(
|
||||
client.generateJson(mockContents, mockSchema),
|
||||
).rejects.toThrow('Failed to parse API response as JSON:');
|
||||
});
|
||||
|
||||
it('should throw an error if generateContent rejects', async () => {
|
||||
const mockContents: Content[] = [
|
||||
{ role: 'user', parts: [{ text: 'test prompt' }] },
|
||||
];
|
||||
const mockSchema = {
|
||||
type: Type.OBJECT,
|
||||
properties: { key: { type: Type.STRING } },
|
||||
};
|
||||
const apiError = new Error('API call failed');
|
||||
|
||||
mockGenerateContent.mockRejectedValue(apiError);
|
||||
|
||||
await expect(
|
||||
client.generateJson(mockContents, mockSchema),
|
||||
).rejects.toThrow(`Failed to generate JSON content: ${apiError.message}`);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: Add tests for startChat and sendMessageStream later
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { toolRegistry } from './tools/tool-registry.js';
|
||||
|
||||
describe('cli tests', () => {
|
||||
it('should have a tool registry', () => {
|
||||
expect(toolRegistry).toBeDefined();
|
||||
expect(typeof toolRegistry.registerTool).toBe('function');
|
||||
});
|
||||
});
|
|
@ -9,7 +9,8 @@
|
|||
"target": "ES2020",
|
||||
"paths": {
|
||||
"@gemini-code/*": ["./packages/*"]
|
||||
}
|
||||
},
|
||||
"types": ["node", "vitest/globals"]
|
||||
},
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["src"],
|
||||
|
|
|
@ -8,14 +8,15 @@
|
|||
"build": "tsc --build && cp package.json dist/",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "eslint . --ext .ts,.tsx",
|
||||
"format": "prettier --write ."
|
||||
"format": "prettier --write .",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.3.3"
|
||||
"typescript": "^5.3.3",
|
||||
"vitest": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { helloServer } from './index.js';
|
||||
|
||||
describe('server tests', () => {
|
||||
it('should export helloServer function', () => {
|
||||
expect(helloServer).toBeDefined();
|
||||
expect(typeof helloServer).toBe('function');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue